diff --git a/Report/appendices/appendix-b.typ b/Report/appendices/appendix-b.typ new file mode 100644 index 0000000..ec14be8 --- /dev/null +++ b/Report/appendices/appendix-b.typ @@ -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). diff --git a/Report/images/IMAGES_REGISTRY.md b/Report/images/IMAGES_REGISTRY.md new file mode 100644 index 0000000..b6a1be0 --- /dev/null +++ b/Report/images/IMAGES_REGISTRY.md @@ -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. diff --git a/Report/images/fig_01_start_sync.png b/Report/images/fig_01_start_sync.png new file mode 100644 index 0000000..8b82b3f Binary files /dev/null and b/Report/images/fig_01_start_sync.png differ diff --git a/Report/images/fig_02_vault_lifecycle.png b/Report/images/fig_02_vault_lifecycle.png new file mode 100644 index 0000000..89bce54 Binary files /dev/null and b/Report/images/fig_02_vault_lifecycle.png differ diff --git a/Report/images/fig_03_navigation_hub.png b/Report/images/fig_03_navigation_hub.png new file mode 100644 index 0000000..3581dc4 Binary files /dev/null and b/Report/images/fig_03_navigation_hub.png differ diff --git a/Report/images/fig_04_domain_class.png b/Report/images/fig_04_domain_class.png new file mode 100644 index 0000000..c410e31 Binary files /dev/null and b/Report/images/fig_04_domain_class.png differ diff --git a/Report/images/fig_05_local_vaults.jpg b/Report/images/fig_05_local_vaults.jpg new file mode 100644 index 0000000..f2b7de3 Binary files /dev/null and b/Report/images/fig_05_local_vaults.jpg differ diff --git a/Report/images/fig_06_encrypt_dialog.jpg b/Report/images/fig_06_encrypt_dialog.jpg new file mode 100644 index 0000000..23a9558 Binary files /dev/null and b/Report/images/fig_06_encrypt_dialog.jpg differ diff --git a/Report/images/fig_07_open_close_dialog.jpg b/Report/images/fig_07_open_close_dialog.jpg new file mode 100644 index 0000000..bfd219f Binary files /dev/null and b/Report/images/fig_07_open_close_dialog.jpg differ diff --git a/Report/images/fig_08_rename_delete_dialog.jpg b/Report/images/fig_08_rename_delete_dialog.jpg new file mode 100644 index 0000000..201f75b Binary files /dev/null and b/Report/images/fig_08_rename_delete_dialog.jpg differ diff --git a/Report/images/fig_09_remote_vaults.jpg b/Report/images/fig_09_remote_vaults.jpg new file mode 100644 index 0000000..7da6093 Binary files /dev/null and b/Report/images/fig_09_remote_vaults.jpg differ diff --git a/Report/images/fig_10_yandex_oauth.jpg b/Report/images/fig_10_yandex_oauth.jpg new file mode 100644 index 0000000..a3213cd Binary files /dev/null and b/Report/images/fig_10_yandex_oauth.jpg differ diff --git a/Report/images/fig_11_room_schema.png b/Report/images/fig_11_room_schema.png new file mode 100644 index 0000000..d66727f Binary files /dev/null and b/Report/images/fig_11_room_schema.png differ diff --git a/Report/images/fig_12_tasks_screen.jpg b/Report/images/fig_12_tasks_screen.jpg new file mode 100644 index 0000000..70403f8 Binary files /dev/null and b/Report/images/fig_12_tasks_screen.jpg differ diff --git a/Report/images/fig_13_tasks_notification.jpg b/Report/images/fig_13_tasks_notification.jpg new file mode 100644 index 0000000..b235cbc Binary files /dev/null and b/Report/images/fig_13_tasks_notification.jpg differ diff --git a/Report/images/fig_14_context_system.png b/Report/images/fig_14_context_system.png new file mode 100644 index 0000000..2594788 Binary files /dev/null and b/Report/images/fig_14_context_system.png differ diff --git a/Report/images/fig_15_bpmn_vault.png b/Report/images/fig_15_bpmn_vault.png new file mode 100644 index 0000000..5be2472 Binary files /dev/null and b/Report/images/fig_15_bpmn_vault.png differ diff --git a/Report/images/fig_16_dfd_level0.png b/Report/images/fig_16_dfd_level0.png new file mode 100644 index 0000000..ebdfc71 Binary files /dev/null and b/Report/images/fig_16_dfd_level0.png differ diff --git a/Report/images/fig_17_use_case.png b/Report/images/fig_17_use_case.png new file mode 100644 index 0000000..fea1721 Binary files /dev/null and b/Report/images/fig_17_use_case.png differ diff --git a/Report/images/fig_18_deployment.png b/Report/images/fig_18_deployment.png new file mode 100644 index 0000000..a53b867 Binary files /dev/null and b/Report/images/fig_18_deployment.png differ diff --git a/Report/images/fig_19_clean_architecture.png b/Report/images/fig_19_clean_architecture.png new file mode 100644 index 0000000..824e288 Binary files /dev/null and b/Report/images/fig_19_clean_architecture.png differ diff --git a/Report/images/fig_20_oauth_sequence.png b/Report/images/fig_20_oauth_sequence.png new file mode 100644 index 0000000..921392d Binary files /dev/null and b/Report/images/fig_20_oauth_sequence.png differ diff --git a/Report/images/fig_21_encrypt_flow.png b/Report/images/fig_21_encrypt_flow.png new file mode 100644 index 0000000..c6eb0d3 Binary files /dev/null and b/Report/images/fig_21_encrypt_flow.png differ diff --git a/Report/images/fig_22_cjm_vault.png b/Report/images/fig_22_cjm_vault.png new file mode 100644 index 0000000..e0ba21a Binary files /dev/null and b/Report/images/fig_22_cjm_vault.png differ diff --git a/Report/images/fig_23_module_deps.png b/Report/images/fig_23_module_deps.png new file mode 100644 index 0000000..0e4e11d Binary files /dev/null and b/Report/images/fig_23_module_deps.png differ diff --git a/Report/images/fig_27_gradle_domain_test.png b/Report/images/fig_27_gradle_domain_test.png new file mode 100644 index 0000000..b84fcfe Binary files /dev/null and b/Report/images/fig_27_gradle_domain_test.png differ diff --git a/Report/images/fig_28_gradle_usecases_test.png b/Report/images/fig_28_gradle_usecases_test.png new file mode 100644 index 0000000..b7863e3 Binary files /dev/null and b/Report/images/fig_28_gradle_usecases_test.png differ diff --git a/Report/images/fig_29_gradle_ui_test.png b/Report/images/fig_29_gradle_ui_test.png new file mode 100644 index 0000000..f7b4e8d Binary files /dev/null and b/Report/images/fig_29_gradle_ui_test.png differ diff --git a/Report/images/fig_30_gradle_test_summary.png b/Report/images/fig_30_gradle_test_summary.png new file mode 100644 index 0000000..ac5a060 Binary files /dev/null and b/Report/images/fig_30_gradle_test_summary.png differ diff --git a/Report/images/fig_31_gradle_connected_test.png b/Report/images/fig_31_gradle_connected_test.png new file mode 100644 index 0000000..747e9ad Binary files /dev/null and b/Report/images/fig_31_gradle_connected_test.png differ diff --git a/Report/images/fig_32_manual_test_checklist.png b/Report/images/fig_32_manual_test_checklist.png new file mode 100644 index 0000000..5fdede5 Binary files /dev/null and b/Report/images/fig_32_manual_test_checklist.png differ diff --git a/Report/includes/abbreviations.typ b/Report/includes/abbreviations.typ new file mode 100644 index 0000000..003dad1 --- /dev/null +++ b/Report/includes/abbreviations.typ @@ -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], +) diff --git a/Report/includes/abstract-en.typ b/Report/includes/abstract-en.typ new file mode 100644 index 0000000..1431872 --- /dev/null +++ b/Report/includes/abstract-en.typ @@ -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. diff --git a/Report/includes/ch01.typ b/Report/includes/ch01.typ new file mode 100644 index 0000000..9793e06 --- /dev/null +++ b/Report/includes/ch01.typ @@ -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], [Очередь фоновых задач: шифрование, синхронизация, отображение прогресса], +) + +==== Управление локальными 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)], +) + +Обзор подтверждает актуальность концепции 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, текстовые секреты, синхронизация) зафиксированы как дополнительные функции второго этапа практики. Полный текст ТЗ — в приложении Б. diff --git a/Report/includes/ch02.typ b/Report/includes/ch02.typ new file mode 100644 index 0000000..b776fb4 --- /dev/null +++ b/Report/includes/ch02.typ @@ -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`], +) + +== Проектирование подсистемы синхронизации + +Подсистема синхронизации спроектирована как набор независимых операций над журналом изменений каждого `Storage`. Алгоритм выбирает «победителя» по ревизии, копирует или удаляет файлы на целевом хранилище, не расшифровывая данные на сервере. Блокировки предотвращают одновременную синхронизацию одной группы; при отмене задачи блокировки снимаются (покрыто unit-тестами `syncGroupCooperativeCancellationReleasesLocks` и др., гл. 5). + +== Нефункциональные архитектурные решения + +#pz-table( + [Нефункциональные решения], + 2, + table.header([Атрибут], [Решение]), + [Производительность], [Потоковое шифрование, фоновые задачи в `:task-runtime`], + [Безопасность], [AES, проверка ключа без полного decrypt, скрытие служебных путей], + [Расширяемость], [`vault-contracts`, регистрация провайдеров], + [Сопровождаемость], [Модульные тесты 68 + листинги в приложении А], + [Надёжность], [Room-транзакции, восстановление журнала после сбоя записи], +) + +== Вывод + +Спроектирована клиентская архитектура с разделением domain, infrastructure и presentation, единым интерфейсом vault и заделом под синхронизацию без передачи ключей. Диаграммы зафиксированы в приложении Г. diff --git a/Report/includes/ch03.typ b/Report/includes/ch03.typ new file mode 100644 index 0000000..3753da0 --- /dev/null +++ b/Report/includes/ch03.typ @@ -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], +) + +=== 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 и неверном ключе формулируются нейтрально, без раскрытия внутренних путей и имён файлов. + +== Вывод + +Спроектированы пользовательские потоки и экранная структура, согласованные с архитектурой и требованиями безопасности. Иллюстрации интерфейса приведены в приложении В. diff --git a/Report/includes/ch04-expand.typ b/Report/includes/ch04-expand.typ new file mode 100644 index 0000000..c1b5098 --- /dev/null +++ b/Report/includes/ch04-expand.typ @@ -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). diff --git a/Report/includes/ch04-modules.typ b/Report/includes/ch04-modules.typ new file mode 100644 index 0000000..18d787a --- /dev/null +++ b/Report/includes/ch04-modules.typ @@ -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). diff --git a/Report/includes/ch04.typ b/Report/includes/ch04.typ new file mode 100644 index 0000000..41fb62d --- /dev/null +++ b/Report/includes/ch04.typ @@ -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" diff --git a/Report/includes/ch05-encryptor.typ b/Report/includes/ch05-encryptor.typ new file mode 100644 index 0000000..01894e5 --- /dev/null +++ b/Report/includes/ch05-encryptor.typ @@ -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], [+], +) + +Методика: для потоков используется `ByteArrayOutputStream` с запасом ёмкости `dataLen*3`, чтобы учесть расширение ciphertext. + +=== Детальное описание тестов StorageSyncEngineTest + +Движок синхронизации тестируется на in-memory двойниках хранилищ. Полный реестр методов — в таблице модуля `:usecases` выше. diff --git a/Report/includes/ch05-sync-tests.typ b/Report/includes/ch05-sync-tests.typ new file mode 100644 index 0000000..be17cf7 --- /dev/null +++ b/Report/includes/ch05-sync-tests.typ @@ -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], [Пустой журнал], [+], +) + +Тесты `StorageSyncJournalMergeTest` и `StorageSyncEncryptionCompatTest` дополняют движок проверкой слияния журнала и совместимости шифрования в группе. diff --git a/Report/includes/ch05-tests-generated.typ b/Report/includes/ch05-tests-generated.typ new file mode 100644 index 0000000..69928be --- /dev/null +++ b/Report/includes/ch05-tests-generated.typ @@ -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], +) + +=== Реестр тестов модуля :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], +) + +=== Реестр тестов модуля :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], +) + +=== Реестр тестов модуля :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], +) + +=== Реестр тестов модуля :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], +) + +=== Реестр тестов модуля :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], +) + diff --git a/Report/includes/ch05.typ b/Report/includes/ch05.typ new file mode 100644 index 0000000..d79af86 --- /dev/null +++ b/Report/includes/ch05.typ @@ -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 + выборочный ручной], [Отсутствие блокирующих дефектов], +) + +=== Матрица тестовых сценариев + +Матрица связывает требования (гл. 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)], +) + +=== Критерии начала и окончания + +*Начало:* собраны модули `: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` каждого модуля], +) + +== Модульные тесты (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], +) + +Прогон `./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], +) + +Запуск: `./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], +) + +#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], [Полная синхронизация с облаком — в разработке], [Открыт], +) + +=== Связь тестов с требованиями + +#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], [Очередь и экран задач], +) + +=== Рекомендации по сопровождению тестов + +При изменении криптомодуля обязателен прогон `: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 к демонстрации и развитию в рамках ВКР. diff --git a/Report/includes/ch06.typ b/Report/includes/ch06.typ new file mode 100644 index 0000000..563aedc --- /dev/null +++ b/Report/includes/ch06.typ @@ -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], +) + +#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], [Защита практики, итоговая сборка ПЗ], +) + +Календарный график работ совпадает с дневником практики: этап 1 — аналитика и проектирование (февраль–март 2026); этап 2 — реализация (29.03–19.04.2026); оформление документации — апрель–май 2026. + +== Модель применения и окупаемости + +*Сценарий применения* — внедрение в ООО НМФ «Нейротех» как внутренний инструмент защищённого хранения файлов сотрудников на корпоративных или личных облачных аккаунтах при сохранении zero-knowledge модели. + +*Эффект*: снижение риска утечки при компрометации провайдера; отсутствие затрат на собственный сервер приложения. Окупаемость для учебно-промышленного прототипа выражается не в прямой прибыли, а в сокращении рисков и возможности доработки продукта без смены архитектуры. + +== Вывод + +Экономическая оценка показывает низкие прямые затраты на создание прототипа и потенциальную практическую ценность для организации-партнёра. Детальный рыночный анализ приведён в п. 1.3.5; полноценный раздел ТЭО в объёме отраслевых ВКР не требуется согласно рекомендациям кафедры. diff --git a/Report/includes/common.typ b/Report/includes/common.typ new file mode 100644 index 0000000..5c40597 --- /dev/null +++ b/Report/includes/common.typ @@ -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) +] diff --git a/Report/includes/conclusion.typ b/Report/includes/conclusion.typ new file mode 100644 index 0000000..964cc20 --- /dev/null +++ b/Report/includes/conclusion.typ @@ -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 без ручного ввода; публикация актуальных скриншотов вместо учебных заглушек; подготовка акта внедрения (прил. Д) при эксплуатации в Нейротех. diff --git a/Report/includes/intro.typ b/Report/includes/intro.typ new file mode 100644 index 0000000..7b47643 --- /dev/null +++ b/Report/includes/intro.typ @@ -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 и в настоящем введении. diff --git a/Report/listings/.gitignore b/Report/listings/.gitignore new file mode 100644 index 0000000..9ab870d --- /dev/null +++ b/Report/listings/.gitignore @@ -0,0 +1 @@ +generated/ diff --git a/Report/listings/listings.config.yaml b/Report/listings/listings.config.yaml new file mode 100644 index 0000000..517448c --- /dev/null +++ b/Report/listings/listings.config.yaml @@ -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 diff --git a/Report/references.bib b/Report/references.bib new file mode 100644 index 0000000..61f2987 --- /dev/null +++ b/Report/references.bib @@ -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 ЮФУ; доступ для проверки по запросу руководителя ВКР}, +} diff --git a/Report/scripts/build-pz.sh b/Report/scripts/build-pz.sh new file mode 100755 index 0000000..8f3d857 --- /dev/null +++ b/Report/scripts/build-pz.sh @@ -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" diff --git a/Report/scripts/check_images.py b/Report/scripts/check_images.py new file mode 100644 index 0000000..fdf501c --- /dev/null +++ b/Report/scripts/check_images.py @@ -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()) diff --git a/Report/scripts/gen_listings.py b/Report/scripts/gen_listings.py new file mode 100644 index 0000000..30ba512 --- /dev/null +++ b/Report/scripts/gen_listings.py @@ -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") \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()) diff --git a/Report/scripts/gen_test_tables.py b/Report/scripts/gen_test_tables.py new file mode 100644 index 0000000..e97c497 --- /dev/null +++ b/Report/scripts/gen_test_tables.py @@ -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() diff --git a/Report/Другие отчёты Wallenc/puml/Domain_Диаграмма_классов.png b/Report/Другие отчёты Wallenc/puml/Domain_Диаграмма_классов.png new file mode 100644 index 0000000..49c3e9e Binary files /dev/null and b/Report/Другие отчёты Wallenc/puml/Domain_Диаграмма_классов.png differ diff --git a/Report/Другие отчёты Wallenc/puml/domain-classdiagram.png b/Report/Другие отчёты Wallenc/puml/domain-classdiagram.png new file mode 100644 index 0000000..c410e31 Binary files /dev/null and b/Report/Другие отчёты Wallenc/puml/domain-classdiagram.png differ diff --git a/Report/Другие отчёты Wallenc/puml/domain-classdiagram.puml b/Report/Другие отчёты Wallenc/puml/domain-classdiagram.puml new file mode 100644 index 0000000..33f9cb3 --- /dev/null +++ b/Report/Другие отчёты Wallenc/puml/domain-classdiagram.puml @@ -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> + } + class ManageLocalVaultUseCase { + + getLocalStorages(): StateFlow> + + 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> + + enqueue(String, PipelineWork): TaskId + + getPipelineState(): StateFlow + + getForegroundUi(): StateFlow + + 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 + } + 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 + + getUuid(): UUID + + getAccessor(): IStorageAccessor + + isAvailable(): StateFlow + + getSize(): StateFlow + + getNumberOfFiles(): StateFlow + + clearAllContent(): Unit + + getMetaInfo(): StateFlow + + isVirtualStorage(): boolean + } + class IVaultsManager { + + getLocalVault(): IVault + + removeRemoteVault(UUID): Unit + + addYandexVault(String): Unit + + getRemoteVaults(): StateFlow> + + getAllStorages(): StateFlow> + + getAllVaults(): StateFlow> + + getUnlockManager(): IUnlockManager + } + class IStorageMetaInfo { + + getEncInfo(): StorageEncryptionInfo + + getName(): String + + getLastModified(): Instant + } + class IFile { + + getMetaInfo(): IMetaInfo + } + class IStorageInfo { + + isEmpty(): Flow + + getUuid(): UUID + + isAvailable(): StateFlow + + getSize(): StateFlow + + getNumberOfFiles(): StateFlow + + getMetaInfo(): StateFlow + + isVirtualStorage(): boolean + } + class IStorageExplorer { + + getCurrentPath(): StateFlow + } + class IVaultInfo { + + getAvailableSpace(): StateFlow + + getType(): VaultType + + getTotalSpace(): StateFlow + + getUuid(): UUID + + isAvailable(): StateFlow + + getStorages(): StateFlow> + } + class IUnlockManager { + + close(IStorage): Unit + + close(UUID): Unit + + open(IStorage, EncryptKey, boolean): Unit + + getOpenedStorages(): StateFlow> + } + class IDirectory { + + getMetaInfo(): IMetaInfo + + getElementsCount(): Integer + } + class IStorageAccessor { + + getFilesFlow(String): Flow>> + + getDirs(String): Unit + + getDirsUpdates(): SharedFlow>> + + touchDir(String): Unit + + getFiles(String): Unit + + getSize(): StateFlow + + isAvailable(): StateFlow + + 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>> + + getNumberOfFiles(): StateFlow + + getFilesUpdates(): SharedFlow>> + + setHidden(String, boolean): Unit + + getFileInfo(String): Unit + } + class IVault { + + getAvailableSpace(): StateFlow + + getType(): VaultType + + getTotalSpace(): StateFlow + + remove(IStorage): Unit + + createStorage(StorageEncryptionInfo): Unit + + getUuid(): UUID + + isAvailable(): StateFlow + + getStorages(): StateFlow> + + 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 + } +} + +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 diff --git a/Report/Другие отчёты Wallenc/puml/wallenc-01-start-and-sync.puml b/Report/Другие отчёты Wallenc/puml/wallenc-01-start-and-sync.puml new file mode 100644 index 0000000..e5b7e97 --- /dev/null +++ b/Report/Другие отчёты Wallenc/puml/wallenc-01-start-and-sync.puml @@ -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 diff --git a/Report/Другие отчёты Wallenc/puml/wallenc-02-vault-lifecycle.puml b/Report/Другие отчёты Wallenc/puml/wallenc-02-vault-lifecycle.puml new file mode 100644 index 0000000..80a5441 --- /dev/null +++ b/Report/Другие отчёты Wallenc/puml/wallenc-02-vault-lifecycle.puml @@ -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 diff --git a/Report/Другие отчёты Wallenc/puml/wallenc-03-navigation-hub.puml b/Report/Другие отчёты Wallenc/puml/wallenc-03-navigation-hub.puml new file mode 100644 index 0000000..448d927 --- /dev/null +++ b/Report/Другие отчёты Wallenc/puml/wallenc-03-navigation-hub.puml @@ -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 diff --git a/Report/Другие отчёты Wallenc/puml/wallenc_01_start_and_sync.png b/Report/Другие отчёты Wallenc/puml/wallenc_01_start_and_sync.png new file mode 100644 index 0000000..8b82b3f Binary files /dev/null and b/Report/Другие отчёты Wallenc/puml/wallenc_01_start_and_sync.png differ diff --git a/Report/Другие отчёты Wallenc/puml/wallenc_02_vault_lifecycle.png b/Report/Другие отчёты Wallenc/puml/wallenc_02_vault_lifecycle.png new file mode 100644 index 0000000..89bce54 Binary files /dev/null and b/Report/Другие отчёты Wallenc/puml/wallenc_02_vault_lifecycle.png differ diff --git a/Report/Другие отчёты Wallenc/puml/wallenc_03_navigation_hub.png b/Report/Другие отчёты Wallenc/puml/wallenc_03_navigation_hub.png new file mode 100644 index 0000000..3581dc4 Binary files /dev/null and b/Report/Другие отчёты Wallenc/puml/wallenc_03_navigation_hub.png differ diff --git a/Report/Другие отчёты Wallenc/Отчёт 1 этап.md b/Report/Другие отчёты Wallenc/Отчёт 1 этап.md new file mode 100644 index 0000000..afabb7b --- /dev/null +++ b/Report/Другие отчёты Wallenc/Отчёт 1 этап.md @@ -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: определены предметная область, архитектурная модель, структура данных, технологический стек и требования к безопасности. +Подготовленные материалы позволяют переходить к реализации следующего этапа — построению функционального ядра приложения и расширению сценариев работы с хранилищами, включая синхронизацию зашифрованных данных с удалёнными провайдерами. diff --git a/Report/Другие отчёты Wallenc/Отчёт 2 этап.md b/Report/Другие отчёты Wallenc/Отчёт 2 этап.md new file mode 100644 index 0000000..ea3373c --- /dev/null +++ b/Report/Другие отчёты Wallenc/Отчёт 2 этап.md @@ -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:** +![Локальные vault](images/СКРИНШОТ №1 — локальные vault в приложении.jpg) + +### 2) Разработка мобильного приложения на Kotlin (Android) + +В пользовательском интерфейсе реализованы ключевые операции работы с vault: + +- отображение списка vault; +- переименование и удаление vault; +- включение шифрования vault; +- открытие зашифрованного vault с использованием мастер-ключа; +- просмотр параметров vault (состояние, служебные сведения, статус шифрования); +- модуль работы с содержимым vault; +- блокировка/закрытие vault. + +Реализованы диалоговые сценарии подтверждения и настройки операций, что повысило управляемость и предсказуемость действий пользователя. + +Интерфейсная часть построена таким образом, чтобы пользователь видел текущее состояние хранилища (зашифровано/не зашифровано, открыто/закрыто) и мог выполнить требуемое действие без перехода в технические служебные экраны. Это улучшило эргономику взаимодействия и снизило число лишних шагов в типовых сценариях. + +**Скриншоты диалогов и операций:** +![Включение шифрования](images/СКРИНШОТ №2 — диалог включения шифрования vault.jpg) +![Открытие и закрытие vault](images/СКРИНШОТ №3 — диалог открытия⁄закрытия зашифрованного vault.jpg) +![Переименование и удаление](images/СКРИНШОТ №4 — диалог переименования⁄удаления vault.jpg) + +### 3) Реализация модуля работы с небезопасными хранилищами через адаптеры + +Реализован модуль адаптерного взаимодействия с типами хранилищ, не требующий собственного серверного контура приложения. +Подход через адаптеры позволил сохранить единый интерфейс работы с vault и упростить расширение списка поддерживаемых провайдеров. + +Технически это важно для дальнейшего масштабирования проекта: добавление нового типа внешнего провайдера не требует переработки пользовательской логики и не нарушает принципы слоистой архитектуры. + +### 4) Взаимодействие мобильного клиента с внешними провайдерами через API/SDK + +На текущем этапе реализована авторизация через Яндекс (OAuth-сценарий) и интеграция соответствующего пользовательского потока в приложение. +Выполнена подготовка слоя удалённых vault и связанных сущностей для дальнейшего расширения удалённых операций. + +Важно, что реализованный контур уже обеспечивает прикладной сценарий идентификации пользователя во внешнем провайдере и формирует корректную основу для дальнейшего расширения функциональности удалённых операций. + +**Скриншоты удалённого контура:** +![Экран удалённых vault](images/СКРИНШОТ №5 — экран удалённых vault.jpg) +![Добавление удалённого vault и авторизация](images/СКРИНШОТ №6 — диалог добавления удалённого vault ⁄ авторизация через Яндекс.jpg) + +### 5) Реализация хранения ключевой информации и метаданных с использованием Room + +Реализован слой локальной БД на Room для хранения: + +- таблиц сопоставления ключевой информации и идентификаторов хранилищ; +- таблиц метаданных vault и состояния хранилищ; +- данных, необходимых для восстановления состояния приложения. + +Структура БД интегрирована в общий цикл работы приложения и используется как системный слой управления состоянием. + +Использование Room позволило обеспечить устойчивое хранение служебной информации между запусками приложения и централизовать доступ к данным через DAO-слой. + +**Визуализация Room БД:** +![Схема Room базы данных](images/ИЗОБРАЖЕНИЕ №7 — визуализация Room базы данных.png) + +### 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, готового к дальнейшему расширению функциональности удалённой работы с хранилищами и развитию синхронизационных механизмов. + +В целом второй этап можно считать успешно завершённым: ключевые задачи реализованы, протестированы и представлены в форме, пригодной для демонстрации и последующего развития проекта на следующем этапе практики. diff --git a/Report/Другие отчёты Wallenc/Производственная_практика_Wallenc.md b/Report/Другие отчёты Wallenc/Производственная_практика_Wallenc.md new file mode 100644 index 0000000..34265c3 --- /dev/null +++ b/Report/Другие отчёты Wallenc/Производственная_практика_Wallenc.md @@ -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 в приложении:** + +![СКРИНШОТ №1 — локальные vault в приложении](<../Отчёт 2 этап/images/СКРИНШОТ №1 — локальные vault в приложении.jpg>) + +### 3.3. Разработка мобильного приложения на Kotlin (Android) + +В UI реализованы операции: + +- отображение **списка** vault; +- **переименование** и **удаление**; +- **включение шифрования**; +- **открытие** зашифрованного vault с использованием мастер-ключа; +- **просмотр параметров** vault (состояние, служебные сведения, статус шифрования); +- модуль работы с **содержимым** vault; +- **блокировка/закрытие** vault. + +Реализованы **диалоговые** сценарии подтверждения и настройки операций. Интерфейс построен так, чтобы пользователь видел текущее состояние (зашифровано/не зашифровано, открыто/закрыто) и выполнял действия **без** перехода на технические служебные экраны — это улучшает эргономику и снижает число лишних шагов. + +**Иллюстрации — диалоги включения шифрования, открытия/закрытия зашифрованного vault, переименования и удаления:** + +![СКРИНШОТ №2 — диалог включения шифрования vault](<../Отчёт 2 этап/images/СКРИНШОТ №2 — диалог включения шифрования vault.jpg>) + +![СКРИНШОТ №3 — диалог открытия/закрытия зашифрованного vault](<../Отчёт 2 этап/images/СКРИНШОТ №3 — диалог открытия⁄закрытия зашифрованного vault.jpg>) + +![СКРИНШОТ №4 — диалог переименования/удаления vault](<../Отчёт 2 этап/images/СКРИНШОТ №4 — диалог переименования⁄удаления vault.jpg>) + +### 3.4. Модуль работы с небезопасными хранилищами через адаптеры + +Реализован модуль **адаптерного** взаимодействия с типами хранилищ **без** собственного серверного контура приложения. Подход через адаптеры сохраняет **единый интерфейс** работы с vault и упрощает расширение списка провайдеров. Добавление нового типа внешнего провайдера **не требует** переработки пользовательской логики и **не нарушает** слоистую архитектуру. + +### 3.5. Взаимодействие с внешними провайдерами через API/SDK + +Реализована **авторизация через Яндекс** (OAuth) и интеграция пользовательского потока в приложение. Подготовлен слой **удалённых** vault и связанных сущностей для дальнейшего расширения удалённых операций. Реализованный контур обеспечивает сценарий **идентификации** пользователя во внешнем провайдере и формирует основу для расширения удалённой функциональности. + +**Иллюстрации — экран удалённых vault и сценарий добавления с авторизацией через Яндекс:** + +![СКРИНШОТ №5 — экран удалённых vault](<../Отчёт 2 этап/images/СКРИНШОТ №5 — экран удалённых vault.jpg>) + +![СКРИНШОТ №6 — диалог добавления удалённого vault и авторизация через Яндекс](<../Отчёт 2 этап/images/СКРИНШОТ №6 — диалог добавления удалённого vault ⁄ авторизация через Яндекс.jpg>) + +### 3.6. Хранение ключевой информации и метаданных (Room) + +Реализован слой локальной БД для: + +- таблиц сопоставления **ключевой информации** и идентификаторов хранилищ; +- таблиц **метаданных** vault и состояния хранилищ; +- данных, необходимых для **восстановления состояния** приложения. + +Структура БД интегрирована в общий цикл работы приложения как **системный** слой управления состоянием. Room обеспечивает устойчивое хранение служебной информации между запусками и **централизованный** доступ через DAO. + +**Иллюстрация — визуализация схемы Room (служебные сущности приложения):** + +![ИЗОБРАЖЕНИЕ №7 — визуализация Room базы данных](<../Отчёт 2 этап/images/ИЗОБРАЖЕНИЕ №7 — визуализация Room базы данных.png>) + +### 3.7. Тестирование приложения + +Параллельно с ручной проверкой экранов велся учёт задач в трекере: на экране задач отображались этапы работ по проекту, а push-уведомление фиксировало обновление статусов — это упорядочивало регрессию сценариев и снижало риск пропустить проверку после изменений в коде. + +**Иллюстрации — экран задач и уведомление о статусе задач:** + +![СКРИНШОТ №7 — экран задач](<../Отчёт 2 этап/images/СКРИНШОТ №7 — Экран задач.jpg>) + +![СКРИНШОТ №8 — уведомление со статусом задач](<../Отчёт 2 этап/images/СКРИНШОТ №8 — Уведомление со статусом задач.jpg>) + +- **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. Старт приложения и параллельная синхронизация + +![Диаграмма 1 — старт Wallenc и фоновая синхронизация по таймеру]() + +### 5.2. Жизненный цикл vault и постановка в очередь синхронизации + +![Диаграмма 2 — жизненный цикл vault и коммиты/очередь UUID]() + +### 5.3. Навигация с главного экрана и независимый SyncWorker + +![Диаграмма 3 — главный экран и фоновый SyncWorker]() + +### 5.4. Диаграмма классов модуля `domain` + +Сгенерирована из **скомпилированных** `.class` (Kotlin → JVM) плагином **code-atlas** (`./gradlew :domain:generateDiagrams`), исходник PlantUML: `puml/domain-classdiagram.puml`. + +![Диаграмма классов domain]() + +--- + +## Заключение + +По результатам **первого** этапа сформирована проектная база 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). + +--- + +*Конец отчёта.* diff --git a/Report/Инструкции/2024_2025_Рекомендации_по_подготовке_ВКР. Часть 1.pdf b/Report/Инструкции/2024_2025_Рекомендации_по_подготовке_ВКР. Часть 1.pdf new file mode 100644 index 0000000..caf94a2 Binary files /dev/null and b/Report/Инструкции/2024_2025_Рекомендации_по_подготовке_ВКР. Часть 1.pdf differ diff --git a/Report/Инструкции/2024_2025_Рекомендации_по_подготовке_ВКР. Часть 3-1.pdf b/Report/Инструкции/2024_2025_Рекомендации_по_подготовке_ВКР. Часть 3-1.pdf new file mode 100644 index 0000000..5156d8e Binary files /dev/null and b/Report/Инструкции/2024_2025_Рекомендации_по_подготовке_ВКР. Часть 3-1.pdf differ diff --git a/Report/Инструкции/Практика. Вводное занятие - Постановка задачи по тестированию.pdf b/Report/Инструкции/Практика. Вводное занятие - Постановка задачи по тестированию.pdf new file mode 100644 index 0000000..acad0c8 Binary files /dev/null and b/Report/Инструкции/Практика. Вводное занятие - Постановка задачи по тестированию.pdf differ diff --git a/Report/Пояснительная_записка_ПытковРЕ.typ b/Report/Пояснительная_записка_ПытковРЕ.typ new file mode 100644 index 0000000..1d01307 --- /dev/null +++ b/Report/Пояснительная_записка_ПытковРЕ.typ @@ -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") diff --git a/Report/Пример работы с Typst.typ b/Report/Пример работы с Typst.typ new file mode 100644 index 0000000..cfd351b --- /dev/null +++ b/Report/Пример работы с Typst.typ @@ -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: [Пример таблицы с данными], +) + +== Блоки кода + +Чтобы оформить блоки кода в документе, можно использовать синтаксис, похожий на Markdown. Пример указан на листинге +@example-code: + +#figure( + ```typst + // Пример кода на Typst: + #import "package.typ" + + // Функция вывода сообщения + print("Hello, world!") + ```, + caption: [Пример кода на Typst], +) + +Этот блок кода демонстрирует базовую структуру программы на 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 $ + + + +$ mat(1, 2, 3; 4, 5, 6; 7, 8, 9) * x = pi * psi(aleph/x) $ + + +$ cal(A) := { x in RR | x "натуральное" } $ + + +== Изображения + +Изображения добавляются с помощью функции `image()` внутри макроса `#figure`. Пример вставки изображения указан на +рисунке @example-image. + +#figure( + image("images/home.jpg", width: 40%), + caption: "Пример изображения", +) + += Работа с аргументами шаблона в gost.with + +Функция `gost.with` принимает множество аргументов, позволяющих настраивать титульную страницу и другие элементы +документа. + +Рассмотрим основные возможности: + +- Обязательные и необязательные аргументы. Многие параметры могут быть опущены, если вы не хотите, чтобы соответствующая + информация отображалась на титульном листе. +- Параметр auto (автоматический год). В аргументах, таких как `approved-by.year` или `year`, можно использовать значение + `auto`. + +- Примеры аргументов: В заголовке шаблона задаются такие параметры, как: + + `ministry-name` – название министерства. + + `organization` – информация об учреждении (полное и сокращённое наименования). + + `performers` – список исполнителей с указанием их позиций. + +== Работа с аргументами шаблона + +Как уже отмечалось, функция `gost.with` позволяет детально настраивать титульную страницу. Рассмотрим ключевые моменты: + +- При передаче параметра `year: auto` текущий год будет вычисляться автоматически. +- Если не требуется выводить определённый элемент (например, номер инвентаризации), соответствующий аргумент можно не + указывать. +- Все аргументы передаются в виде именованных параметров, что обеспечивает гибкость конфигурации. + += Заключение + +Шаблон modern-g7-32 для Typst значительно упрощает подготовку документов, соответствующих государственным стандартам. Он +обеспечивает удобное создание таблиц, блоков кода и изображений, а гибкая настройка через функцию `gost.with` позволяет +автоматически подставлять дату и исключать ненужные элементы с титульной страницы. Используйте данный шаблон для +создания качественных и стандартизированных документов @petrov2021analiz. + +#bibliography("references.bib") + +#show: appendixes + += Изображения в приложениях +Пример вставки изображения в приложение указан на рисунке @appendix-image. + +#figure( + image("images/nature.jpg", width: 40%), + caption: "Пример изображения", +) + +== Изображения в под-приложениях +Пример вставки изображения в под-приложение указан на рисунке @appendix-image-3. #figure( + image("images/abstract.jpg", width: 40%), + caption: "Пример изображения", +) + += Блоки кода +Чтобы оформить блоки кода в документе, можно использовать синтаксис, похожий на Markdown. Пример указан на листинге +@appendix-code: + +#figure( + ```typst + // Пример кода на Typst: + #import "package.typ" + + // Функция вывода сообщения + print("Hello, world!") + ```, + caption: [Пример кода на Typst], +) + += Формулы +Чтобы оформить формулы в документе, можно использовать синтаксис typst-math. Примеры демонстрируют использование формул +в шаблоне: + +$ sum_(k=0)^n k = 1 + ... + n = (n(n+1)) / 2 $ + +как оформлять таблицы сказано в приложении @appendix-tables, а также в разделе с нумерацией @with-numbering. + +== Таблицы +Для создания таблиц используется функция `table()`, обёрнутая в макрос `#figure` для добавления подписи. Пример показан +на таблице @appendix-table. + +#figure( + table( + columns: 4, + table.header([Заголовок 1], [Заголовок 2], [Заголовок 3], [Заголовок 4]), + [Проверка], [Проверка], [Проверка], [Проверка], + [Проверка], [Проверка], [Проверка], [Проверка], + ), + caption: [Пример таблицы с данными], +) + +#appendix-heading("справочное", level: 1)[Приложение с указанием статуса] +#lorem(100) + +#appendix-heading("справочное", level: 2)[Приложение второго уровня со статусом] +#lorem(100) diff --git a/Report/Чужие примеры/MarkovaDK_VKR.docx.pdf b/Report/Чужие примеры/MarkovaDK_VKR.docx.pdf new file mode 100644 index 0000000..5db7029 Binary files /dev/null and b/Report/Чужие примеры/MarkovaDK_VKR.docx.pdf differ