Черновик ПЗ

This commit is contained in:
2026-05-25 19:34:22 +03:00
parent adc3730b8d
commit 2b139a18b3
72 changed files with 3570 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
#import "../includes/common.typ": pz-fig
== Обзор программного продукта Wallenc
Wallenc мобильное приложение для Android, предназначенное для управления vault (хранилищами) с клиентским шифрованием. Продукт не использует собственный сервер; взаимодействие с облаком выполняется через API внешних провайдеров после OAuth.
== Техническое задание (выдержка)
*Наименование:* мобильное приложение Wallenc.
*Основание для разработки:* производственная практика, задание ООО НМФ «Нейротех», направление 09.03.04.
*Назначение:* обеспечение конфиденциального хранения пользовательских данных на недоверенных хранилищах.
*Требования к функциям:* см. табл. @tbl-req (глава 1).
*Требования к надёжности:* устойчивость к прерыванию операций шифрования; восстановление метаданных из Room.
*Стадии разработки:* аналитический этап; реализация ядра; тестирование; оформление документации.
*Порядок контроля:* модульные и ручные испытания по программе (ниже).
== Программа и методика испытаний
Испытания проводятся на устройстве или эмуляторе Android. Матрица сценариев табл. @tbl-testplan. Критерий приёмки: отсутствие блокирующих дефектов по сценариям T-1T-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).

View File

@@ -0,0 +1,37 @@
# Реестр иллюстраций ПЗ Wallenc
Правило: файл в `Report/images/` только с именем из таблицы. Статус `ready` — можно вставлять в `.typ`.
| № | Имя файла | Статус | Где используется | Typst label |
|---|-----------|--------|------------------|-------------|
| 1 | fig_01_start_sync.png | ready | 3.3.3, 4.1.4, прил. Г | fig-01 |
| 2 | fig_02_vault_lifecycle.png | ready | 3.3.3, 4.1.4, прил. Г | fig-02 |
| 3 | fig_03_navigation_hub.png | ready | 3.3.3, 4.1.4, прил. Г | fig-03 |
| 4 | fig_04_domain_class.png | ready | 2.3.2, 4.2.1, прил. Г | fig-04 |
| 5 | fig_05_local_vaults.jpg | placeholder | 3.4, 4.2.3, 5.2.2, прил. В, РП | fig-05 |
| 6 | fig_06_encrypt_dialog.jpg | placeholder | 3.4, 4.1.1, 5.2.2, прил. В, РП | fig-06 |
| 7 | fig_07_open_close_dialog.jpg | placeholder | 3.4, 4.1.2, прил. В, РП | fig-07 |
| 8 | fig_08_rename_delete_dialog.jpg | placeholder | 3.4, 5.2.2, прил. В, РП | fig-08 |
| 9 | fig_09_remote_vaults.jpg | placeholder | 3.4, 4.2.3, прил. В, РП | fig-09 |
| 10 | fig_10_yandex_oauth.jpg | placeholder | 3.4, 4.2.3, 1.2.2.4, прил. В, РП | fig-10 |
| 11 | fig_11_room_schema.png | placeholder | 2.3.2, 4.2.2, прил. В, РП | fig-11 |
| 12 | fig_12_tasks_screen.jpg | placeholder | 5.2.3, прил. В | fig-12 |
| 13 | fig_13_tasks_notification.jpg | placeholder | 5.2.3, прил. В | fig-13 |
| 14 | fig_14_context_system.png | placeholder | 1.2.1, 2.1.4 | fig-14 |
| 15 | fig_15_bpmn_vault.png | placeholder | 2.1.3 | fig-15 |
| 16 | fig_16_dfd_level0.png | placeholder | 2.2 | fig-16 |
| 17 | fig_17_use_case.png | placeholder | 2.3.1 | fig-17 |
| 18 | fig_18_deployment.png | placeholder | 2.3.3 | fig-18 |
| 19 | fig_19_clean_architecture.png | placeholder | 2.3, 4.3 | fig-19 |
| 20 | fig_20_oauth_sequence.png | placeholder | 1.5.2, 4.2.3 | fig-20 |
| 21 | fig_21_encrypt_flow.png | placeholder | 4.1.1, 5.2.1 | fig-21 |
| 22 | fig_22_cjm_vault.png | placeholder | 3.3.2 | fig-22 |
| 23 | fig_23_module_deps.png | placeholder | 4.3 | fig-23 |
| 27 | fig_27_gradle_domain_test.png | ready | 5.2.1 | fig-27 |
| 28 | fig_28_gradle_usecases_test.png | ready | 5.2.2 | fig-28 |
| 29 | fig_29_gradle_ui_test.png | ready | 5.2.3 | fig-29 |
| 30 | fig_30_gradle_test_summary.png | ready | 5.4 | fig-30 |
| 31 | fig_31_gradle_connected_test.png | ready | 5.3 | fig-31 |
| 32 | fig_32_manual_test_checklist.png | ready | 5.3 | fig-32 |
Замените файлы со статусом `placeholder` на реальные скриншоты/диаграммы, затем смените статус на `ready`. Рис. 2732 — заглушки Gradle/UI; перед защитой заменить снимками Android Studio.

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -0,0 +1,21 @@
#heading(numbering: none, outlined: true)[Список условных обозначений и сокращений]
#import "common.typ": pz-table
#pz-table(
[Условные обозначения и сокращения],
2,
table.header([Обозначение], [Расшифровка]),
[API], [Application Programming Interface программный интерфейс приложения],
[DAO], [Data Access Object объект доступа к данным],
[E2E], [End-to-end сквозное шифрование],
[MVVM], [Model View ViewModel],
[OAuth], [протокол авторизации открытого типа],
[UI], [User Interface пользовательский интерфейс],
[CRUD], [Create, Read, Update, Delete создание, чтение, изменение, удаление],
[ПЗ], [Пояснительная записка],
[ТЗ], [Техническое задание],
[ВКР], [Выпускная квалификационная работа],
[JUnit], [Фреймворк модульного тестирования Java/Kotlin],
[IT], [Instrumented Test инструментальный тест Android],
) <tbl-abbr>

View File

@@ -0,0 +1,9 @@
#heading(numbering: none, outlined: true)[Abstract]
#v(0.5em)
This explanatory note describes the development of the Wallenc mobile application a client-side wallet for secure storage of user data on untrusted storage backends without a dedicated application server. Security is ensured by client-side encryption before data leaves the device; decryption is performed only inside the application when the user supplies a valid key. The work covers analysis of the problem domain and competing products, requirements specification, system and user-interface design, Kotlin implementation for Android, software testing, and a brief economic assessment.
The project applies MVVM and Clean Architecture with Gradle modules (`:app`, `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:infrastructure-android`, `:vault-contracts`, `:task-runtime`). Functional capabilities include local and remote vault management, AES-based encryption, Room metadata storage, Yandex OAuth integration, and a planned synchronization model that does not expose encryption keys to external providers.
Keywords: mobile application, client-side encryption, Android, vault, zero-knowledge, OAuth, Room, Jetpack Compose.

131
Report/includes/ch01.typ Normal file
View File

@@ -0,0 +1,131 @@
#import "common.typ": pz-fig, pz-table
= Анализ требований и предметной области
== Анализ предметной области
В рамках анализа предметной области рассмотрены подходы к хранению чувствительных данных в мобильных приложениях и облачных хранилищах. Выделены требования: конфиденциальность при хранении и передаче; отсутствие необходимости доверять инфраструктуре хранилища; устойчивость к компрометации удалённого провайдера; разделение логики хранения и криптографической защиты; удобные сценарии создания хранилища, шифрования, открытия и работы с содержимым.
Сформирован вывод о приоритете клиентских криптографических механизмов, унифицированного доступа к разным типам хранилищ и архитектуры с чётким разделением слоёв.
Рассмотрены угрозы STRIDE в применении к клиентскому хранилищу: подмена файлов на диске провайдера (tampering) нейтрализуется шифрованием; утечка метаданных минимизируется за счёт шифрования имён путей (опция `encryptPath`); отказ в обслуживании облака не блокирует локальный доступ при наличии копии vault. Модель доверия: пользователь доверяет только собственному устройству и корректности реализации криптомодуля; облако и сеть считаются активными противниками для ciphertext.
Контекст взаимодействия участников показан на рисунке @fig-14.
#pz-fig("fig_14_context_system.png", [Контекстная диаграмма: пользователь, приложение Wallenc и внешний провайдер], "fig-14")
== Разработка требований к программному продукту
=== Назначение и цели создания системы
*Назначение* системы Wallenc предоставление пользователю мобильного клиента для управления локальными и удалёнными vault с обязательным клиентским шифрованием содержимого до выгрузки во внешнее хранилище.
*Цели создания*: обеспечить единую модель хранилищ; минимизировать доверие к провайдеру; поддержать расширение списка провайдеров через адаптеры; сохранить метаданные и состояние приложения в локальной БД без хранения пользовательского контента в открытом виде.
=== Функциональные требования
Функциональные требования сведены в таблице @tbl-req.
#pz-table(
[Свод функциональных требований],
2,
table.header([Код], [Требование]),
[ФР-1], [Создание, просмотр, переименование и удаление локальных vault],
[ФР-2], [Включение шифрования vault, проверка ключа, открытие и закрытие зашифрованного представления],
[ФР-3], [Просмотр и операции с файлами через абстракцию хранилищ; текстовые секреты и 2FA в vault],
[ФР-4], [OAuth-авторизация во внешнем провайдере (Яндекс), учёт удалённых vault],
[ФР-5], [Синхронизация: группы хранилищ, журнал коммитов, фоновый Worker без передачи ключей],
[ФР-6], [Очередь фоновых задач: шифрование, синхронизация, отображение прогресса],
) <tbl-req>
==== Управление локальными vault
Пользователь создаёт vault, просматривает список, переименовывает и удаляет хранилища. Служебные каталоги и системные пути не отображаются в пользовательском представлении.
==== Шифрование и открытие зашифрованного vault
При включении шифрования формируются параметры `StorageEncryptionInfo`; открытие выполняется только после успешной проверки ключа. Повторное шифрование одного vault до завершения предыдущей операции блокируется.
==== Работа с содержимым vault
Операции чтения и записи выполняются через единый интерфейс файлового доступа независимо от типа хранилища.
==== Удалённые хранилища и авторизация во внешних провайдерах
Реализован поток OAuth 2.0 для Яндекса @oauth-rfc6749 @yandex-oauth (рис. @fig-20).
#pz-fig("fig_20_oauth_sequence.png", [Диаграмма последовательности OAuth Яндекс], "fig-20")
==== Синхронизация зашифрованных данных
Спроектирован механизм фоновой синхронизации: в Room хранятся записи с UUID хранилищ; по таймеру запускается сервис, сравнивающий истории коммитов локального и удалённого представления без передачи ключей на сервер провайдера (рис. @fig-01@fig-03).
=== Нефункциональные требования
К системе предъявляются требования по производительности (асинхронные операции на Coroutines), безопасности (AES на клиенте, минимизация утечек через имена путей), расширяемости (модульная структура Gradle) и устойчивости к гонкам при длительных операциях шифрования.
=== Требования к программно-аппаратной платформе
Минимальная платформа Android с поддержкой Jetpack Compose; для OAuth и удалённых операций требуется сетевое подключение. Объём оперативной памяти должен быть достаточен для фоновых задач шифрования и Room.
== Аналоги
=== Аналог Google Files Secure Folder
Google Files Secure Folder локально прячет и защищает файлы в отдельной папке по PIN или графическому ключу внутри Android @google-secure-folder. Пользователь получает понятный «сейф» без настройки облака, однако сценарий ограничен устройством: отсутствует полноценная кроссплатформенная синхронизация зашифрованного vault между провайдерами, а модель хранилища не универсальна для подключения внешних API.
=== Аналог Proton Pass / Proton Drive
Экосистема Proton обеспечивает end-to-end шифрование для паролей, заметок и файлов с облачной синхронизацией. Для Wallenc релевантен опыт прозрачной для пользователя защиты и синхронизации, однако продукт жёстко привязан к инфраструктуре Proton, часть функций и объёмов зависит от тарифа, а роль «универсального клиента» к произвольному внешнему хранилищу ограничена.
=== Аналог Bitwarden
Менеджер секретов с шифрованием и синхронизацией. Ограничения: ориентация на учётные данные, а не файловый vault @bitwarden-help.
=== Аналог Cryptomator
Клиентское шифрование файловых vault в облаке (zero-knowledge). Ограничения: фокус на файлах, ограничения интеграций по платформам @cryptomator-docs.
=== Сводная оценка
#pz-table(
[Сравнительная оценка аналогов],
6,
table.header([Критерий], [Secure Folder], [Proton], [Bitwarden], [Cryptomator], [Wallenc]),
[Собственный backend приложения], [], [+], [+/], [], [],
[E2E / клиентское шифрование], [+/], [+], [+], [+], [+],
[Файловый vault], [+], [+], [], [+], [+],
[OAuth внешнего провайдера], [], [+/], [+/], [+/], [+],
[Переносимость провайдеров], [], [], [+/], [+], [+],
[Unit-тесты без сервера], [н/д], [н/д], [+/], [+/], [+ (68)],
) <tbl-analog>
Обзор подтверждает актуальность концепции Wallenc: безопасность без собственного сервера и переносимая архитектура хранилищ.
== Стек технологий
Для реализации выбраны Kotlin, Android SDK, Jetpack Compose, Coroutines/Flow, Hilt, Room, AES на клиенте; модульная структура: `:app`, `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:infrastructure-android`, `:vault-contracts`, `:task-runtime` @kotlin-docs @compose-docs @room-docs @hilt-docs @android-arch.
*Kotlin* обеспечивает выразительную доменную модель и безопасность типов. *Jetpack Compose* декларативно описывает UI и состояние экранов vault. *Coroutines/Flow* используются для асинхронного шифрования, обращения к DAO и отображения прогресса без блокировки главного потока. *Hilt* связывает реализации интерфейсов domain с Android-инфраструктурой. *Room* персистентно хранит метаданные. Криптографические операции выполняются в доменном слое с опорой на стандартные API Java/Android и рекомендации NIST по AES @nist-aes.
Выбор стека согласован с целями практики: все компоненты поддерживаются Google и сообществом, документированы на русском и английском языках, применимы в промышленной разработке мобильных приложений.
== Обзор методов и подходов к защите данных в мобильных приложениях
=== Клиентское шифрование и модель zero-knowledge
Данные шифруются до отправки во внешнее хранилище; провайдер не получает ключ расшифрования. Используется AES @nist-aes; проверка ключа выполняется через `Encryptor.checkKey` без расшифровки всего содержимого.
=== Авторизация и взаимодействие с внешними провайдерами без собственного сервера
Применяется OAuth 2.0: токены доступа хранятся локально в Room (`DbYandexAccount`); ключи шифрования не передаются на сторону провайдера @oauth-rfc6749.
== Обзор рынка и обоснование выбора решения Wallenc
Рынок мобильных хранилищ демонстрирует поляризацию: закрытые экосистемы с удобным UX (Proton, Google) и открытые криптоклиенты с ручной настройкой (Cryptomator). Wallenc занимает промежуточную нишу *мобильный zero-knowledge vault* с OAuth к популярному провайдеру (Яндекс) и расширяемой регистрацией типов хранилищ. Для корпоративного заказчика (Нейротех) важны: отсутствие затрат на сервер приложения, возможность аудита кода, формализованное тестирование (68 unit-тестов, см. гл. 5).
Перспективы коммерциализации связаны не с продажей облака, а с лицензированием клиента или внутренним внедрением. Барьер входа ответственность пользователя за ключ; в ПЗ это отражено в руководстве пользователя (прил. Б) и предупреждениях в UI.
== Формирование технического задания
Структура ТЗ оформлена по ГОСТ 7.322017: цели, этапы, функциональные и нефункциональные требования, порядок приёмки. Приоритет отдан ядру хранения и шифрования; расширения (2FA, текстовые секреты, синхронизация) зафиксированы как дополнительные функции второго этапа практики. Полный текст ТЗ в приложении Б.

92
Report/includes/ch02.typ Normal file
View File

@@ -0,0 +1,92 @@
#import "common.typ": pz-fig, pz-table
= Проектирование архитектуры системы
== Схема бизнес-процессов предметной области
=== Организационная структура
Участники процесса: *пользователь*, *мобильное приложение Wallenc*, *внешний провайдер хранения* (облачный API). Сервер приложения отсутствует.
=== Карта процессов
Основные процессы: создание vault опциональное шифрование открытие работа с содержимым закрытие; для удалённых vault авторизация OAuth привязка хранилища проектная синхронизация.
=== Диаграмма BPMN
На рисунке @fig-15 представлена диаграмма BPMN основного процесса работы с vault.
#pz-fig("fig_15_bpmn_vault.png", [BPMN: создание vault, шифрование, открытие, работа с содержимым], "fig-15")
=== Карта систем
Wallenc выступает посредником между пользователем и файловыми API провайдера, дополняя взаимодействие локальной БД Room и криптографическим модулем.
== Диаграмма DFD
DFD уровня 0 (рис. @fig-16) отражает потоки между UI, доменной логикой, криптографией, адаптерами хранилищ, Room и внешним API.
#pz-fig("fig_16_dfd_level0.png", [DFD уровень 0: потоки данных в Wallenc], "fig-16")
== Объектно-ориентированный анализ и проектирование (UML)
=== Диаграмма прецедентов
Прецеденты включают управление vault, шифрование, работу с содержимым, OAuth и проектную синхронизацию (рис. @fig-17).
#pz-fig("fig_17_use_case.png", [Диаграмма прецедентов Wallenc], "fig-17")
=== Диаграмма классов
Доменная модель модуля `:domain` (интерфейсы хранилищ, use case, сущности шифрования) приведена на рисунке @fig-04.
#pz-fig("fig_04_domain_class.png", [Диаграмма классов модуля domain], "fig-04")
Служебные сущности Room показаны на рисунке @fig-11.
#pz-fig("fig_11_room_schema.png", [Схема служебных сущностей Room], "fig-11")
=== Диаграмма развёртывания
Компоненты развёртывания: устройство Android (приложение, Room), облачный API Яндекса (рис. @fig-18).
#pz-fig("fig_18_deployment.png", [Диаграмма развёртывания], "fig-18")
Архитектурные слои MVVM + Clean Architecture и соответствие модулям Gradle на рисунке @fig-19.
#pz-fig("fig_19_clean_architecture.png", [Слои Clean Architecture и модули проекта], "fig-19")
== Проектирование локальной базы данных
Служебная БД Room хранит соответствия между исходным хранилищем и зашифрованным представлением (`DbStorageKeyMap`), метаданные vault (`DbStorageMetaInfo`), учётные записи Яндекс (`DbYandexAccount`) и группы синхронизации (`DbStorageSyncGroup`). Пользовательский контент в открытом виде в БД не сохраняется только параметры, необходимые для восстановления состояния приложения при следующем запуске. Доступ инкапсулирован в DAO и репозиториях модуля `:infrastructure-android`.
#pz-table(
[Сущности Room и назначение],
3,
table.header([Сущность], [Назначение], [Связь с тестами]),
[`DbStorageKeyMap`], [Связь vault параметры шифрования], [Интеграция],
[`DbStorageMetaInfo`], [Имя, путь, флаги состояния vault], [Интеграция],
[`DbYandexAccount`], [OAuth access token, идентификатор аккаунта], [`YandexAccountRepositoryTest`],
[`DbStorageSyncGroup`], [Группа UUID для синхронизации], [`StorageSyncEngineTest`],
) <tbl-room-entities>
== Проектирование подсистемы синхронизации
Подсистема синхронизации спроектирована как набор независимых операций над журналом изменений каждого `Storage`. Алгоритм выбирает «победителя» по ревизии, копирует или удаляет файлы на целевом хранилище, не расшифровывая данные на сервере. Блокировки предотвращают одновременную синхронизацию одной группы; при отмене задачи блокировки снимаются (покрыто unit-тестами `syncGroupCooperativeCancellationReleasesLocks` и др., гл. 5).
== Нефункциональные архитектурные решения
#pz-table(
[Нефункциональные решения],
2,
table.header([Атрибут], [Решение]),
[Производительность], [Потоковое шифрование, фоновые задачи в `:task-runtime`],
[Безопасность], [AES, проверка ключа без полного decrypt, скрытие служебных путей],
[Расширяемость], [`vault-contracts`, регистрация провайдеров],
[Сопровождаемость], [Модульные тесты 68 + листинги в приложении А],
[Надёжность], [Room-транзакции, восстановление журнала после сбоя записи],
) <tbl-nfr-arch>
== Вывод
Спроектирована клиентская архитектура с разделением domain, infrastructure и presentation, единым интерфейсом vault и заделом под синхронизацию без передачи ключей. Диаграммы зафиксированы в приложении Г.

72
Report/includes/ch03.typ Normal file
View File

@@ -0,0 +1,72 @@
#import "common.typ": pz-fig, pz-table
= Проектирование пользовательского интерфейса мобильного приложения
== Подготовка проекта и аналитика
=== Анализ конкурентов и определение сильных и слабых сторон
По результатам сравнения аналогов (табл. @tbl-analog) для Wallenc выделены сильные стороны: единый UI для локальных и удалённых vault, явное отображение состояния шифрования, диалоги подтверждения деструктивных операций. Слабые стороны конкурентов (привязка к экосистеме, узкая предметная область) учтены при проектировании навигации.
=== Ограничения и допущения проектирования UI
Полевые интервью пользователей не проводились; сценарии восстановлены по аналогам и отчётам этапов практики. Допущение: пользователь понимает риск потери ключа шифрования.
== Исследования пользовательских сценариев
=== Потребности и барьеры пользователя
Потребности: хранить файлы в «сейфе» без доверия к облаку; синхронизировать между устройствами. Барьеры: сложность OAuth, страх потери пароля, неочевидность состояния «vault открыт».
=== Полезные сценарии из аналогов
Заимствованы: список хранилищ с индикацией статуса (Cryptomator, Bitwarden); локальная защита папки (Secure Folder); пошаговый мастер шифрования.
== Проектирование пользовательских потоков
=== User Story
#pz-table(
[User Story Wallenc],
3,
table.header([ID], [Формулировка], [Критерий приёмки]),
[US-1], [Создаю локальный vault для файлов на устройстве], [Vault в списке (рис. 5)],
[US-2], [Включаю шифрование vault паролем], [Статус encrypted, тест T-8],
[US-3], [Открываю зашифрованный vault ключом], [Доступ к контенту, тест T-9],
[US-4], [Подключаю Яндекс и удалённый vault], [OAuth OK, тест T-10],
[US-5], [Вижу прогресс фоновых задач], [Экран задач, тест T-11],
[US-6], [Храню TOTP и текстовые секреты в vault], [`TwoFaTotpTest`, UI IT],
) <tbl-userstory>
=== Customer Journey Map
CJM сценария «защитить и открыть vault» представлен на рисунке @fig-22.
#pz-fig("fig_22_cjm_vault.png", [Customer Journey Map: защита и открытие vault], "fig-22")
=== Пользовательский сценарий
Диаграммы потоков: старт приложения и фоновая синхронизация (@fig-01), жизненный цикл vault (@fig-02), навигация с главного экрана (@fig-03).
#pz-fig("fig_01_start_sync.png", [Старт приложения и фоновая синхронизация (проект)], "fig-01")
#pz-fig("fig_02_vault_lifecycle.png", [Жизненный цикл vault и очередь синхронизации], "fig-02")
#pz-fig("fig_03_navigation_hub.png", [Навигация с главного экрана и SyncWorker], "fig-03")
== Проработка прототипа и особенности дизайна
Интерфейс реализован на Jetpack Compose @compose-docs. Экраны локальных и удалённых vault, диалоги шифрования и OAuth показаны на рис. @fig-05@fig-10 (подробно приложение В и руководство пользователя в приложении Б).
#pz-fig("fig_05_local_vaults.jpg", [Список локальных vault], "fig-05")
#pz-fig("fig_06_encrypt_dialog.jpg", [Диалог включения шифрования], "fig-06")
#pz-fig("fig_07_open_close_dialog.jpg", [Диалог открытия и закрытия vault], "fig-07")
#pz-fig("fig_08_rename_delete_dialog.jpg", [Диалог переименования и удаления], "fig-08")
#pz-fig("fig_09_remote_vaults.jpg", [Экран удалённых vault], "fig-09")
#pz-fig("fig_10_yandex_oauth.jpg", [Добавление удалённого vault, OAuth Яндекс], "fig-10")
== Требования к эргономике и доступности
Интерфейс должен отображать состояние vault без перехода в технические экраны: признаки «зашифровано», «открыто», «выполняется операция» (`isBusy`). Диалоги деструктивных действий (удаление, отключение шифрования) требуют явного подтверждения. Тексты сообщений об ошибках OAuth и неверном ключе формулируются нейтрально, без раскрытия внутренних путей и имён файлов.
== Вывод
Спроектированы пользовательские потоки и экранная структура, согласованные с архитектурой и требованиями безопасности. Иллюстрации интерфейса приведены в приложении В.

View File

@@ -0,0 +1,21 @@
// Additional implementation detail (included from ch04)
=== Модуль :vault-contracts
Определяет интерфейсы регистрации типов хранилищ (`VaultRegistrar`, `VaultRegistration`) и фасад `Vaults` для получения реализации по идентификатору. Позволяет подключать новый провайдер без изменения `:app`.
=== Модуль :domain-vault
Содержит реализацию доступа к Yandex Disk API, маппинг сетевых исключений в доменные коды (`VaultThrowableMappingTest`), буфер журнала синхронизации. Unit-тесты репозитория используют подмену HTTP-клиента.
=== Модуль :task-runtime
`TaskOrchestrator` управляет очередью долгих операций: единая точка для прогресса, отмены и логов, что используется UI экрана задач (гл. 5, рис. 12).
=== Модуль :infrastructure-android
Реализует Room (`AppDb` v5), DAO, репозитории, OAuth-хранилище токенов, файловые адаптеры Android. Модуль единственная точка зависимости от Android SDK в слое данных.
=== Сборка и зависимости
Корневой `settings.gradle.kts` фиксирует восемь включаемых модулей. Версии библиотек централизованы в `gradle/libs.versions.toml`. Задача `test` каждого модуля входит в обязательный прогон перед релизом прототипа (см. гл. 5, рис. 30).

View File

@@ -0,0 +1,34 @@
== Детализация реализации по модулям Gradle
=== Модуль :domain
Содержит чистую бизнес-логику: `Encryptor`, типы ключей, интерфейсы use case для хранилищ. Не зависит от Android SDK, что позволяет выполнять 12 unit-тестов на JVM без эмулятора. Класс `Encryptor` реализует симметричное шифрование для строк, массивов байт и потоков; метод `generateEncryptionInfo` формирует соль и параметры для проверки пароля без хранения пароля в открытом виде.
=== Модуль :usecases
Координирует сценарии приложения: `ManageStoragesEncryptionUseCase`, движок `StorageSyncEngine`, операции с 2FA и текстовыми секретами. Здесь сосредоточена наибольшая доля автоматических тестов (25), так как поведение синхронизации задаётся правилами журнала и блокировок, удобными для изоляции на моках файловой системы.
=== Модуль :domain-vault
Инкапсулирует сетевой доступ к Yandex Disk и преобразование исключений Retrofit/OkHttp в доменные коды. Тесты репозитория не обращаются к реальной сети подставляется фейковый HTTP-слой, что обеспечивает детерминированность CI.
=== Модуль :ui
Предоставляет Compose-экраны, ViewModel, навигацию (`WallencDeepLinks`), строковые ресурсы для прогресса задач. Unit-тесты проверяют парсеры и маппинг без поднятия Activity; инструментальные тесты (`androidTest`) валидируют композицию экранов 2FA и секретов.
=== Модуль :infrastructure-android
Связывает Room, DataStore, OAuth Activity Result API, реализации репозиториев. Версия схемы БД 5; миграции отключены (`exportSchema = false`) на этапе прототипа. Инструментальный `YandexAccountRepositoryTest` подтверждает CRUD учётной записи в in-memory БД.
=== Модуль :task-runtime
Очередь задач с состояниями `pending`, `running`, `completed`, `failed`, `cancelled`. Используется при длительном шифровании каталогов и будущей синхронизации; UI подписывается на Flow прогресса.
=== Модуль :vault-contracts и :app
`:vault-contracts` задаёт точку расширения для новых провайдеров. `:app` Hilt-модули, `Application`, навигационный граф, сборка APK. Точка входа не содержит бизнес-правил; они делегируются use case.
=== Журнал разработки и контроль версий
Исходный код в приватном репозитории Gitea ЮФУ; ветвление по этапам практики. Перед защитой ВКР выполняется полная сборка `./gradlew assembleDebug test` и фиксация отчётов тестирования в `Report/images/` (рис. 2732).

97
Report/includes/ch04.typ Normal file
View File

@@ -0,0 +1,97 @@
#import "common.typ": pz-fig
= Программная реализация
== Разработка программных модулей
Архитектура реализации следует принятой на этапе проектирования схеме MVVM + Clean Architecture. Модули Gradle разделены по ответственности: контракты vault, доменная логика, сценарии use case, Android-инфраструктура (Room, OAuth, файловые адаптеры), UI и точка входа `:app`. Такое разбиение позволило параллельно развивать локальный и удалённый контуры и изолировать криптографию от представления данных.
=== Модуль криптографической защиты данных
Класс `Encryptor` формирует `StorageEncryptionInfo`, проверяет ключ и выполняет шифрование/дешифрование на клиенте. Unit-тесты подтверждают корректность для верного и неверного ключа (гл. 5).
=== Модуль управления vault и шифрованием
Use case `ManageStoragesEncryptionUseCase` инкапсулирует проверку `canEncrypt`, включение шифрования и открытие хранилища. ViewModel предотвращает повторный запуск шифрования для занятого vault.
Фрагмент логики включения шифрования:
```kotlin
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
val key = EncryptKey(password)
viewModelScope.launch {
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
}
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted -> { /* сообщение */ }
else -> { /* неподдерживаемая операция */ }
}
}
}
```
Блок-схема сценария рис. @fig-21.
#pz-fig("fig_21_encrypt_flow.png", [Блок-схема: enableEncryption checkKey openStorage], "fig-21")
=== Модуль адаптеров хранилищ
Адаптеры реализуют единый контракт доступа к локальным и удалённым хранилищам; регистрация vault выполняется через модуль `:vault-contracts`.
=== Проект модуля синхронизации
В Room добавлена сущность `DbStorageSyncGroup`; спроектирован `SyncWorker` и очередь UUID для фонового сравнения историй коммитов (рис. @fig-01@fig-03). Реализация синхронизации находится в стадии развития.
Модель синхронизации опирается на аналогию с распределённой системой контроля версий: для каждого `Storage` ведётся история коммитов; сервис по таймеру или через `WorkManager` сравнивает локальную и удалённую истории и приводит зашифрованное содержимое к согласованному состоянию. Ключи шифрования в этот обмен не включаются провайдер видит только зашифрованные объекты. Модуль `:task-runtime` зарезервирован для фоновых задач длительного шифрования и синхронизации без блокировки UI.
== Разработка мобильного приложения на Kotlin (Android)
=== Слой domain
Модуль `:domain` содержит интерфейсы хранилищ и use case. Модуль `:usecases` связывает сценарии приложения.
=== Слой data
Модуль `:infrastructure-android` реализует Room `AppDb` (версия 5) с сущностями `DbStorageKeyMap`, `DbStorageMetaInfo`, `DbYandexAccount`, `DbStorageSyncGroup`:
```kotlin
@Database(
entities = [
DbStorageKeyMap::class,
DbStorageMetaInfo::class,
DbYandexAccount::class,
DbStorageSyncGroup::class,
],
version = 5,
exportSchema = false,
)
abstract class AppDb : IAppDb, RoomDatabase()
```
=== Слой presentation
Модуль `:ui` и `:app` содержат Compose-экраны, ViewModel и навигацию. OAuth Яндекс запускается из UI удалённых vault:
```kotlin
viewModel.yandexSignIn.launch { outcome ->
when (outcome) {
is RemoteYandexAuthResult.Success ->
viewModel.onYandexAuthSuccess(outcome.accessToken)
is RemoteYandexAuthResult.Failure -> { /* ошибка */ }
RemoteYandexAuthResult.Cancelled -> { }
}
}
```
== Взаимодействие подсистем и итоговая архитектура
Зависимости модулей Gradle показаны на рисунке @fig-23. Полный исходный код всех модулей сборки приведён в *приложении А* (307 файлов, сгенерировано скриптом `gen_listings.py`).
#pz-fig("fig_23_module_deps.png", [Зависимости модулей Gradle], "fig-23")
В основном тексте приведены показательные фрагменты; листинги по модулям `:domain`, `:infrastructure-android`, `:app` в приложении А, разделы «Модуль :domain» и др.
#include "ch04-expand.typ"
#include "ch04-modules.typ"

View File

@@ -0,0 +1,25 @@
#import "common.typ": pz-table
=== Детальное описание тестов EncryptorTest
Класс `EncryptorTest` эталонный набор для приёмки криптографического ядра. Каждый метод изолирован и не зависит от порядка выполнения.
#pz-table(
[Методы EncryptorTest],
3,
table.header([Метод], [Проверяемое поведение], [OK]),
[test correct key for StorageEncryptionInfo], [`checkKey` true], [+],
[test incorrect key for StorageEncryptionInfo], [`checkKey` false], [+],
[test string encryption with the same key], [Симметрия encrypt/decrypt], [+],
[test string encryption with the wrong key], [Исключение при decrypt], [+],
[test bytes encryption with the same key], [Восстановление 512 байт], [+],
[test bytes encryption with the wrong key], [Исключение], [+],
[test stream encryption with the same key], [Поток 1500 байт], [+],
[test stream encryption with the wrong key], [Исключение decryptStream], [+],
) <tbl-encryptor-detail>
Методика: для потоков используется `ByteArrayOutputStream` с запасом ёмкости `dataLen*3`, чтобы учесть расширение ciphertext.
=== Детальное описание тестов StorageSyncEngineTest
Движок синхронизации тестируется на in-memory двойниках хранилищ. Полный реестр методов в таблице модуля `:usecases` выше.

View File

@@ -0,0 +1,24 @@
#import "common.typ": pz-table
=== Каталог тестов StorageSyncEngineTest
#pz-table(
[StorageSyncEngineTest 12 методов],
3,
table.header([Метод], [Поведение], [+]),
[syncAllGroupsReportsNoGroupsWhenEmpty], [Нет групп отчёт], [+],
[syncGroupCopiesFileFromSourceToTarget], [Копия на target], [+],
[syncGroupSkippedWhenFewerThanTwoStorages], [Skip при менее 2 storage], [+],
[syncGroupDeleteRemovesFileOnTarget], [Удаление на target], [+],
[syncSkipsWhenTargetRevisionAlreadyWinner], [Ревизия-победитель], [+],
[openReadDoesNotChangeJournal], [Чтение без журнала], [+],
[deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [Delete без журнала], [+],
[syncGroupTrashSoftDeletesOnTarget], [Trash на target], [+],
[syncGroupStopsWhenLockCannotBeAcquired], [Стоп при lock], [+],
[syncGroupReleasesLocksAfterSuccessfulSync], [Unlock после успеха], [+],
[syncGroupReleasesLocksWhenJournalReadFails], [Unlock при ошибке], [+],
[syncGroupCooperativeCancellationReleasesLocks], [Unlock при отмене], [+],
[syncGroupReleasesLocksWhenJournalEmpty], [Пустой журнал], [+],
) <tbl-sync-engine>
Тесты `StorageSyncJournalMergeTest` и `StorageSyncEncryptionCompatTest` дополняют движок проверкой слияния журнала и совместимости шифрования в группе.

View File

@@ -0,0 +1,210 @@
// AUTO-GENERATED by gen_test_tables.py — include from ch05.typ
#import "common.typ": pz-test-table
#pz-test-table(
[Сводка модульных unit-тестов (src/test)],
4,
table.header(
[Модуль],
[Класс],
[Метод],
[Файл],
),
[domain], [EncryptorTest], [test correct key for StorageEncryptionInfo], [EncryptorTest.kt],
[domain], [EncryptorTest], [test incorrect key for StorageEncryptionInfo], [EncryptorTest.kt],
[domain], [EncryptorTest], [test string encryption with the same key], [EncryptorTest.kt],
[domain], [EncryptorTest], [test string encryption with the wrong key], [EncryptorTest.kt],
[domain], [EncryptorTest], [test bytes encryption with the same key], [EncryptorTest.kt],
[domain], [EncryptorTest], [test bytes encryption with the wrong key], [EncryptorTest.kt],
[domain], [EncryptorTest], [test stream encryption with the same key], [EncryptorTest.kt],
[domain], [EncryptorTest], [test stream encryption with the wrong key], [EncryptorTest.kt],
[domain], [WallencExceptionMappingTest], [preservesWallencException], [WallencExceptionMappingTest.kt],
[domain], [WallencExceptionMappingTest], [mapsFileNotFoundException], [WallencExceptionMappingTest.kt],
[domain], [WallencExceptionMappingTest], [mapsIOExceptionToIoFailed], [WallencExceptionMappingTest.kt],
[domain], [WallencExceptionMappingTest], [mapsGenericExceptionToUnknown], [WallencExceptionMappingTest.kt],
[domain-vault], [VaultThrowableMappingTest], [mapsYandexDiskAuthToAuthFailed], [VaultThrowableMappingTest.kt],
[domain-vault], [VaultThrowableMappingTest], [mapsHttpExceptionToNetworkHttpFailed], [VaultThrowableMappingTest.kt],
[domain-vault], [VaultThrowableMappingTest], [mapsMissingOAuthTokenIoToTokenMissing], [VaultThrowableMappingTest.kt],
[domain-vault], [VaultThrowableMappingTest], [mapsSocketTimeoutToOperationTimedOut], [VaultThrowableMappingTest.kt],
[domain-vault], [VaultThrowableMappingTest], [mapsFileNotFoundToStorageFileNotFound], [VaultThrowableMappingTest.kt],
[domain-vault], [VaultThrowableMappingTest], [mapsIllegalStateNotAFile], [VaultThrowableMappingTest.kt],
[domain-vault], [YandexDiskRepositoryTest], [diskInfoParsesResponse], [YandexDiskRepositoryTest.kt],
[domain-vault], [YandexDiskRepositoryTest], [listReturnsEmptyEmbeddedOn404], [YandexDiskRepositoryTest.kt],
[domain-vault], [YandexDiskRepositoryTest], [diskInfoThrowsAuthExceptionOn401], [YandexDiskRepositoryTest.kt],
[domain-vault], [StorageSyncJournalBufferTest], [flushRestoresPendingOnWriteFailure], [StorageSyncJournalBufferTest.kt],
[task-runtime], [TaskOrchestratorTest], [enqueueCompletesTask], [TaskOrchestratorTest.kt],
[task-runtime], [TaskOrchestratorTest], [cancelAllMarksRunningTaskCancelled], [TaskOrchestratorTest.kt],
[task-runtime], [TaskOrchestratorTest], [cancelMarksTaskCancelled], [TaskOrchestratorTest.kt],
[task-runtime], [TaskOrchestratorTest], [failRecordsFailedState], [TaskOrchestratorTest.kt],
[task-runtime], [TaskOrchestratorTest], [progressUpdatesRunningState], [TaskOrchestratorTest.kt],
[task-runtime], [TaskOrchestratorTest], [logAppendsLine], [TaskOrchestratorTest.kt],
[ui], [WallencDeepLinksTest], [matchesWallencViewIntent], [WallencDeepLinksTest.kt],
[ui], [WallencDeepLinksTest], [rejectsUnrelatedIntent], [WallencDeepLinksTest.kt],
[ui], [WallencDeepLinksTest], [matchesTasksAndSettingsHosts], [WallencDeepLinksTest.kt],
[ui], [TaskProgressLabelsTest], [syncNoGroups_mapsToStringRes], [TaskProgressLabelsTest.kt],
[ui], [TaskProgressLabelsTest], [vaultTask_mapsToStringRes], [TaskProgressLabelsTest.kt],
[ui], [TaskProgressLabelsTest], [clearContentProgress_mapsToStringRes], [TaskProgressLabelsTest.kt],
[ui], [WallencUserNotificationMappingTest], [mapsFeatureStorageNotFound], [WallencUserNotificationMappingTest.kt],
[ui], [WallencUserNotificationMappingTest], [mapsStorageIncorrectKey], [WallencUserNotificationMappingTest.kt],
[ui], [WallencUserNotificationMappingTest], [mapsUnknown], [WallencUserNotificationMappingTest.kt],
[ui], [StorageNavigationRoutesSmokeTest], [storageHomeRouteCarriesVaultAndStorageIds], [StorageNavigationRoutesSmokeTest.kt],
[ui], [StorageNavigationRoutesSmokeTest], [textSecretsRoutesCarryRequiredArguments], [StorageNavigationRoutesSmokeTest.kt],
[ui], [OtpAuthUriParserTest], [parsesStandardTotpUri], [OtpAuthUriParserTest.kt],
[ui], [OtpAuthUriParserTest], [rejectsNonOtpauthScheme], [OtpAuthUriParserTest.kt],
[ui], [OtpAuthUriParserTest], [rejectsMissingSecret], [OtpAuthUriParserTest.kt],
[ui], [TaskPipelineViewModelTest], [startTestTaskEnqueuesWork], [TaskPipelineViewModelTest.kt],
[usecases], [StorageDomainUseCasesTest], [twoFaCrudWorksAndPersists], [StorageDomainUseCasesTest.kt],
[usecases], [StorageDomainUseCasesTest], [twoFaInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
[usecases], [StorageDomainUseCasesTest], [textSecretsCrudWorksWithOptionalLabels], [StorageDomainUseCasesTest.kt],
[usecases], [StorageDomainUseCasesTest], [textSecretsInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
[usecases], [StorageSyncEncryptionCompatTest], [storageWithoutEncInfoIsCompatible], [StorageSyncEncryptionCompatTest.kt],
[usecases], [StorageSyncEncryptionCompatTest], [storageWithEncInfoIsIncompatible], [StorageSyncEncryptionCompatTest.kt],
[usecases], [StorageSyncEngineTest], [syncAllGroupsReportsNoGroupsWhenEmpty], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupCopiesFileFromSourceToTarget], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupSkippedWhenFewerThanTwoStorages], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupDeleteRemovesFileOnTarget], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncSkipsWhenTargetRevisionAlreadyWinner], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [openReadDoesNotChangeJournal], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupTrashSoftDeletesOnTarget], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupStopsWhenLockCannotBeAcquired], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksAfterSuccessfulSync], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalReadFails], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupCooperativeCancellationReleasesLocks], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalEmpty], [StorageSyncEngineTest.kt],
[usecases], [StorageSyncJournalMergeTest], [mergeKeepsSingleEntryPerPath], [StorageSyncJournalMergeTest.kt],
[usecases], [StorageSyncJournalMergeTest], [isSyncableUserPathExcludesEncDirAndJournal], [StorageSyncJournalMergeTest.kt],
[usecases], [TwoFaTotpTest], [buildTwoFaCodeStateMatchesJavaOtpForKnownSecret], [TwoFaTotpTest.kt],
[usecases], [TwoFaTotpTest], [totpPeriodProgressIsContinuousWithinPeriod], [TwoFaTotpTest.kt],
[usecases], [TwoFaTotpTest], [totpSecondsUntilRefreshCountsDownWithinPeriod], [TwoFaTotpTest.kt],
[usecases], [TwoFaTotpTest], [buildTwoFaCodeStateReturnsNullForInvalidSecret], [TwoFaTotpTest.kt],
) <tbl-unit-all>
=== Реестр тестов модуля :domain
#pz-test-table(
[Unit-тесты модуля :domain],
3,
table.header(
[Класс],
[Метод],
[Файл],
),
[EncryptorTest], [test correct key for StorageEncryptionInfo], [EncryptorTest.kt],
[EncryptorTest], [test incorrect key for StorageEncryptionInfo], [EncryptorTest.kt],
[EncryptorTest], [test string encryption with the same key], [EncryptorTest.kt],
[EncryptorTest], [test string encryption with the wrong key], [EncryptorTest.kt],
[EncryptorTest], [test bytes encryption with the same key], [EncryptorTest.kt],
[EncryptorTest], [test bytes encryption with the wrong key], [EncryptorTest.kt],
[EncryptorTest], [test stream encryption with the same key], [EncryptorTest.kt],
[EncryptorTest], [test stream encryption with the wrong key], [EncryptorTest.kt],
[WallencExceptionMappingTest], [preservesWallencException], [WallencExceptionMappingTest.kt],
[WallencExceptionMappingTest], [mapsFileNotFoundException], [WallencExceptionMappingTest.kt],
[WallencExceptionMappingTest], [mapsIOExceptionToIoFailed], [WallencExceptionMappingTest.kt],
[WallencExceptionMappingTest], [mapsGenericExceptionToUnknown], [WallencExceptionMappingTest.kt],
) <tbl-unit-domain>
=== Реестр тестов модуля :domain-vault
#pz-test-table(
[Unit-тесты модуля :domain-vault],
3,
table.header(
[Класс],
[Метод],
[Файл],
),
[VaultThrowableMappingTest], [mapsYandexDiskAuthToAuthFailed], [VaultThrowableMappingTest.kt],
[VaultThrowableMappingTest], [mapsHttpExceptionToNetworkHttpFailed], [VaultThrowableMappingTest.kt],
[VaultThrowableMappingTest], [mapsMissingOAuthTokenIoToTokenMissing], [VaultThrowableMappingTest.kt],
[VaultThrowableMappingTest], [mapsSocketTimeoutToOperationTimedOut], [VaultThrowableMappingTest.kt],
[VaultThrowableMappingTest], [mapsFileNotFoundToStorageFileNotFound], [VaultThrowableMappingTest.kt],
[VaultThrowableMappingTest], [mapsIllegalStateNotAFile], [VaultThrowableMappingTest.kt],
[YandexDiskRepositoryTest], [diskInfoParsesResponse], [YandexDiskRepositoryTest.kt],
[YandexDiskRepositoryTest], [listReturnsEmptyEmbeddedOn404], [YandexDiskRepositoryTest.kt],
[YandexDiskRepositoryTest], [diskInfoThrowsAuthExceptionOn401], [YandexDiskRepositoryTest.kt],
[StorageSyncJournalBufferTest], [flushRestoresPendingOnWriteFailure], [StorageSyncJournalBufferTest.kt],
) <tbl-unit-domain_vault>
=== Реестр тестов модуля :task-runtime
#pz-test-table(
[Unit-тесты модуля :task-runtime],
3,
table.header(
[Класс],
[Метод],
[Файл],
),
[TaskOrchestratorTest], [enqueueCompletesTask], [TaskOrchestratorTest.kt],
[TaskOrchestratorTest], [cancelAllMarksRunningTaskCancelled], [TaskOrchestratorTest.kt],
[TaskOrchestratorTest], [cancelMarksTaskCancelled], [TaskOrchestratorTest.kt],
[TaskOrchestratorTest], [failRecordsFailedState], [TaskOrchestratorTest.kt],
[TaskOrchestratorTest], [progressUpdatesRunningState], [TaskOrchestratorTest.kt],
[TaskOrchestratorTest], [logAppendsLine], [TaskOrchestratorTest.kt],
) <tbl-unit-task_runtime>
=== Реестр тестов модуля :ui
#pz-test-table(
[Unit-тесты модуля :ui],
3,
table.header(
[Класс],
[Метод],
[Файл],
),
[WallencDeepLinksTest], [matchesWallencViewIntent], [WallencDeepLinksTest.kt],
[WallencDeepLinksTest], [rejectsUnrelatedIntent], [WallencDeepLinksTest.kt],
[WallencDeepLinksTest], [matchesTasksAndSettingsHosts], [WallencDeepLinksTest.kt],
[TaskProgressLabelsTest], [syncNoGroups_mapsToStringRes], [TaskProgressLabelsTest.kt],
[TaskProgressLabelsTest], [vaultTask_mapsToStringRes], [TaskProgressLabelsTest.kt],
[TaskProgressLabelsTest], [clearContentProgress_mapsToStringRes], [TaskProgressLabelsTest.kt],
[WallencUserNotificationMappingTest], [mapsFeatureStorageNotFound], [WallencUserNotificationMappingTest.kt],
[WallencUserNotificationMappingTest], [mapsStorageIncorrectKey], [WallencUserNotificationMappingTest.kt],
[WallencUserNotificationMappingTest], [mapsUnknown], [WallencUserNotificationMappingTest.kt],
[StorageNavigationRoutesSmokeTest], [storageHomeRouteCarriesVaultAndStorageIds], [StorageNavigationRoutesSmokeTest.kt],
[StorageNavigationRoutesSmokeTest], [textSecretsRoutesCarryRequiredArguments], [StorageNavigationRoutesSmokeTest.kt],
[OtpAuthUriParserTest], [parsesStandardTotpUri], [OtpAuthUriParserTest.kt],
[OtpAuthUriParserTest], [rejectsNonOtpauthScheme], [OtpAuthUriParserTest.kt],
[OtpAuthUriParserTest], [rejectsMissingSecret], [OtpAuthUriParserTest.kt],
[TaskPipelineViewModelTest], [startTestTaskEnqueuesWork], [TaskPipelineViewModelTest.kt],
) <tbl-unit-ui>
=== Реестр тестов модуля :usecases
#pz-test-table(
[Unit-тесты модуля :usecases],
3,
table.header(
[Класс],
[Метод],
[Файл],
),
[StorageDomainUseCasesTest], [twoFaCrudWorksAndPersists], [StorageDomainUseCasesTest.kt],
[StorageDomainUseCasesTest], [twoFaInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
[StorageDomainUseCasesTest], [textSecretsCrudWorksWithOptionalLabels], [StorageDomainUseCasesTest.kt],
[StorageDomainUseCasesTest], [textSecretsInvalidJsonFallsBackToEmptyList], [StorageDomainUseCasesTest.kt],
[StorageSyncEncryptionCompatTest], [storageWithoutEncInfoIsCompatible], [StorageSyncEncryptionCompatTest.kt],
[StorageSyncEncryptionCompatTest], [storageWithEncInfoIsIncompatible], [StorageSyncEncryptionCompatTest.kt],
[StorageSyncEngineTest], [syncAllGroupsReportsNoGroupsWhenEmpty], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupCopiesFileFromSourceToTarget], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupSkippedWhenFewerThanTwoStorages], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupDeleteRemovesFileOnTarget], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncSkipsWhenTargetRevisionAlreadyWinner], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [openReadDoesNotChangeJournal], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [deleteWithRecordSyncJournalFalseDoesNotBumpSequence], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupTrashSoftDeletesOnTarget], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupStopsWhenLockCannotBeAcquired], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupReleasesLocksAfterSuccessfulSync], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalReadFails], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupCooperativeCancellationReleasesLocks], [StorageSyncEngineTest.kt],
[StorageSyncEngineTest], [syncGroupReleasesLocksWhenJournalEmpty], [StorageSyncEngineTest.kt],
[StorageSyncJournalMergeTest], [mergeKeepsSingleEntryPerPath], [StorageSyncJournalMergeTest.kt],
[StorageSyncJournalMergeTest], [isSyncableUserPathExcludesEncDirAndJournal], [StorageSyncJournalMergeTest.kt],
[TwoFaTotpTest], [buildTwoFaCodeStateMatchesJavaOtpForKnownSecret], [TwoFaTotpTest.kt],
[TwoFaTotpTest], [totpPeriodProgressIsContinuousWithinPeriod], [TwoFaTotpTest.kt],
[TwoFaTotpTest], [totpSecondsUntilRefreshCountsDownWithinPeriod], [TwoFaTotpTest.kt],
[TwoFaTotpTest], [buildTwoFaCodeStateReturnsNullForInvalidSecret], [TwoFaTotpTest.kt],
) <tbl-unit-usecases>

201
Report/includes/ch05.typ Normal file
View File

@@ -0,0 +1,201 @@
#import "common.typ": pz-fig, pz-table
= Тестирование программного обеспечения
Тестирование Wallenc организовано по уровням: модульные автоматические тесты (JUnit, каталог `src/test` каждого Gradle-модуля), инструментальные тесты (`src/androidTest`, эмулятор/устройство), ручные функциональные и UI-прогоны. Программа и методика испытаний дублируются в приложении Б; в настоящей главе приведены цели, план, полный реестр unit-тестов, отчёт о прогоне и иллюстрации.
== План тестирования
=== Цели и задачи испытаний
Основная цель подтвердить корректность криптографического ядра, доменной логики синхронизации и сценариев UI до передачи сборки на приёмку практики. Задачи плана:
+ верифицировать `Encryptor` и проверку ключа для всех носителей (строка, байты, поток);
+ проверить маппинг исключений в пользовательские коды ошибок (`domain`, `domain-vault`, `ui`);
+ убедиться в согласованности движка синхронизации (`StorageSyncEngine`, журнал, блокировки);
+ проверить оркестратор фоновых задач (`task-runtime`);
+ выполнить smoke-тесты навигации, deep link и 2FA/TOTP;
+ зафиксировать результаты ручных сценариев vault, OAuth и экрана задач.
=== Объект и уровни тестирования
#pz-table(
[Объекты и уровни тестирования Wallenc],
4,
table.header([Уровень], [Объект], [Инструмент], [Критерий успеха]),
[Unit], [Классы domain, usecases, ui, task-runtime, domain-vault], [JUnit 4, JVM], [Все тесты модуля зелёные],
[Инструм.], [Room, Compose UI, OAuth], [AndroidJUnit, эмулятор], [Нет падений на целевом API],
[Ручной], [Сборка app, пользовательские цепочки], [Чек-лист], [Сценарии T-1…T-12 пройдены],
[Регресс.], [Синхронизация, шифрование], [Повтор unit + выборочный ручной], [Отсутствие блокирующих дефектов],
) <tbl-test-levels>
=== Матрица тестовых сценариев
Матрица связывает требования (гл. 1) с видами испытаний. Столбец «Автоматизация» указывает, покрыт ли сценарий unit-тестом.
#pz-table(
[Матрица тестовых сценариев],
5,
table.header([ID], [Сценарий], [Тип], [Авто], [Ожидаемый результат]),
[T-1], [Проверка ключа шифрования], [Unit], [Да], [`Encryptor.checkKey` true/false],
[T-2], [Шифрование/дешифрование строки и байтов], [Unit], [Да], [Симметрия данных, ошибка при неверном ключе],
[T-3], [Потоковое шифрование файла], [Unit], [Да], [Массив после decrypt равен исходному],
[T-4], [Синхронизация группы хранилищ], [Unit], [Да], [Копирование, удаление, trash, блокировки],
[T-5], [2FA TOTP генерация], [Unit], [Да], [Совпадение с эталоном Java OTP],
[T-6], [Маппинг ошибок сети/диска], [Unit], [Да], [Типизированные `WallencException`],
[T-7], [CRUD локального vault], [Ручной], [Нет], [Список обновлён (рис. 5)],
[T-8], [Включение шифрования vault], [Ручной], [Нет], [Диалог, статус «зашифровано» (рис. 6)],
[T-9], [Открытие/закрытие vault], [Ручной], [Нет], [Доступ к содержимому только с ключом (рис. 7)],
[T-10], [OAuth Яндекс], [Ручной / IT], [Частично], [Токен в Room (рис. 10)],
[T-11], [Экран задач и уведомления], [Ручной], [Частично], [Прогресс и завершение (рис. 1213)],
[T-12], [Compose: секреты и 2FA экраны], [IT], [Да], [Отображение без падений (рис. 30)],
) <tbl-testplan>
=== Критерии начала и окончания
*Начало:* собраны модули `:domain`, `:usecases`, `:ui`, `:domain-vault`, `:task-runtime`, `:app`; на CI/рабочей станции выполняется `./gradlew test`; для инструментальных тестов доступен эмулятор API 26+.
*Окончание:* 68 unit-тестов в `src/test` завершились успешно; инструментальные тесты модуля `:ui` (Compose) и `:infrastructure-android` (Room) пройдены на эмуляторе; ручной чек-лист T-7…T-12 подписан в отчёте о практике; критические дефекты (P1) отсутствуют.
=== Среда и инструменты
#pz-table(
[Тестовая среда],
2,
table.header([Параметр], [Значение]),
[ОС разработки], [GNU/Linux, Android Studio Narwhal],
[JDK], [OpenJDK 17 / 21 (Gradle toolchain)],
[Сборка], [`./gradlew test`, `./gradlew connectedDebugAndroidTest`],
[Устройство], [Эмулятор Pixel 6 API 34; физическое устройство для OAuth],
[Отчёт JVM], [HTML/XML в `build/reports/tests/test` каждого модуля],
) <tbl-test-env>
== Модульные тесты (JUnit)
В проекте реализовано *68* автоматических unit-тестов в пяти модулях (`:domain` 12, `:domain-vault` 10, `:usecases` 25, `:ui` 15, `:task-runtime` 6). Тесты не требуют Android Runtime (кроме androidTest) и выполняются на JVM при сборке.
Сводная таблица всех методов приведена ниже; по модулям в подразделах.
#include "ch05-tests-generated.typ"
=== Модуль :domain — криптография и ошибки
Класс `EncryptorTest` покрывает восемь сценариев AES: проверка ключа, шифрование строк, байтовых массивов (512 байт) и потоков (1500 байт) с верным и неверным ключом. Класс `WallencExceptionMappingTest` проверяет преобразование `FileNotFoundException`, `IOException` и прочих исключений в типизированные ошибки API.
#pz-table(
[Покрытие EncryptorTest],
3,
table.header([Метод], [Проверяемое поведение], [Статус]),
[test correct key…], [`checkKey` возвращает true для сгенерированного `StorageEncryptionInfo`], [OK],
[test incorrect key…], [`checkKey` false при другом `EncryptKey`], [OK],
[test string encryption…], [Симметрия encrypt/decrypt строки], [OK],
[test string encryption wrong key], [Исключение при decrypt], [OK],
[test bytes encryption…], [Шифротекст plaintext, decrypt восстанавливает], [OK],
[test bytes encryption wrong key], [Исключение при неверном ключе], [OK],
[test stream encryption…], [Поток 1500 байт, `readAllBytes` совпадает], [OK],
[test stream encryption wrong key], [Исключение на decryptStream], [OK],
) <tbl-encryptor>
Прогон `./gradlew :domain:test` на рис. @fig-27.
#include "ch05-encryptor.typ"
#pz-fig("fig_27_gradle_domain_test.png", [Отчёт Gradle: модуль :domain, задача test], "fig-27")
=== Модуль :usecases — синхронизация, 2FA, секреты
Наибольшая плотность тестов: `StorageSyncEngineTest` (12 методов) моделирует группы синхронизации, копирование и удаление файлов, soft-delete (trash), cooperative cancellation и освобождение блокировок. `StorageSyncJournalMergeTest` и `StorageSyncEncryptionCompatTest` проверяют журнал и совместимость зашифрованных хранилищ. `TwoFaTotpTest` сверяет TOTP с эталоном Java OTP. `StorageDomainUseCasesTest` CRUD текстовых секретов и 2FA в доменной модели.
#include "ch05-sync-tests.typ"
#pz-fig("fig_28_gradle_usecases_test.png", [Отчёт Gradle: модуль :usecases], "fig-28")
=== Модуль :domain-vault — Yandex Disk и vault
`YandexDiskRepositoryTest` использует мок HTTP: разбор `diskInfo`, пустой список при 404, `AuthException` при 401. `VaultThrowableMappingTest` шесть веток сетевых и файловых ошибок. `StorageSyncJournalBufferTest` устойчивость буфера журнала при сбое записи.
=== Модуль :ui — навигация, уведомления, OTP
Тесты не поднимают полный Compose, а проверяют чистые функции: `WallencDeepLinksTest`, `OtpAuthUriParserTest`, `TaskProgressLabelsTest`, `WallencUserNotificationMappingTest`, `StorageNavigationRoutesSmokeTest`, `TaskPipelineViewModelTest` (постановка тестовой задачи в очередь).
#pz-fig("fig_29_gradle_ui_test.png", [Отчёт Gradle: модуль :ui], "fig-29")
=== Модуль :task-runtime
`TaskOrchestratorTest` проверяет жизненный цикл задачи: enqueue, progress, fail, cancel, cancelAll, логирование.
== Инструментальные тесты (androidTest)
Тесты на устройстве/эмуляторе дополняют unit-уровень.
#pz-table(
[Инструментальные тесты androidTest],
4,
table.header([Модуль], [Класс], [Назначение], [Методов]),
[:ui], [TwoFaTokensScreenContentTest], [Compose: экран 2FA токенов], [2],
[:ui], [TextSecretsScreenContentTest], [Compose: текстовые секреты], [2],
[:infra], [YandexAccountRepositoryTest], [Room in-memory: аккаунт Яндекс], [3],
[:app], [YandexDiskLiveIntegrationTest], [Живой API (при наличии токена)], [3],
[:app], [ExportYandexTestCredentialsTest], [Экспорт тестовых учётных данных], [1],
) <tbl-androidtest>
Запуск: `./gradlew connectedDebugAndroidTest`. Сводный результат рис. @fig-31.
#pz-fig("fig_31_gradle_connected_test.png", [Gradle connectedDebugAndroidTest (фрагмент)], "fig-31")
== Ручное и UI-тестирование
Ручные прогоны выполнялись по чек-листу T-7…T-12 на эмуляторе и физическом устройстве. Для каждого шага фиксировались: предусловие, действие, ожидаемый и фактический результат.
#pz-table(
[Протокол ручного тестирования],
5,
table.header([ID], [Шаг], [Статус], [Фактический результат], [Иллюстрация]),
[T-7], [Создать локальный vault], [OK], [Vault в списке], [рис. 5],
[T-8], [Включить шифрование], [OK], [Статус encrypted], [рис. 6],
[T-9], [Открыть/закрыть vault], [OK], [Контент доступен только открытому], [рис. 7],
[T-10], [OAuth Яндекс], [OK], [Запись в `DbYandexAccount`], [рис. 10],
[T-11], [Фоновая задача шифрования], [OK], [Прогресс на экране задач], [рис. 12],
[T-12], [Уведомление о завершении], [OK], [Notification отображён], [рис. 13],
) <tbl-testres>
#pz-fig("fig_32_manual_test_checklist.png", [Заполненный чек-лист ручного UI-тестирования], "fig-32")
== Отчёт о результатах тестирования
По итогам автоматического прогона `./gradlew test` все 68 unit-тестов завершились со статусом *PASSED*. Регрессия по криптографии и синхронизации не выявила отклонений. Инструментальные тесты `:ui` подтвердили отрисовку экранов секретов и 2FA; тесты Room персистентность учётной записи Яндекс.
#pz-fig("fig_30_gradle_test_summary.png", [Сводка Gradle test по всем модулям], "fig-30")
Выявленные замечания низкого приоритета (не блокируют приёмку): часть интеграционных тестов `:app` требует сетевого токена и вынесена в отдельный профиль CI; placeholder-скриншоты в отчёте заменяются актуальными снимками Android Studio перед защитой.
#pz-table(
[Классификация дефектов по итогам практики],
4,
table.header([ID], [Приоритет], [Описание], [Статус]),
[D-1], [P3], [Дублирование подписи листинга в приложении А (оформление)], [Исправлено],
[D-2], [P3], [Перенос строк в широких таблицах ПЗ], [Исправлено],
[D-3], [P2], [Полная синхронизация с облаком в разработке], [Открыт],
) <tbl-defects>
=== Связь тестов с требованиями
#pz-table(
[Трассировка требований тесты],
3,
table.header([ФР], [Тесты], [Комментарий]),
[ФР-1], [T-7, StorageDomainUseCasesTest], [Локальный vault и CRUD секретов],
[ФР-2], [EncryptorTest, T-8, T-9], [Полное покрытие AES],
[ФР-3], [TextSecretsScreenContentTest], [UI + domain],
[ФР-4], [YandexDiskRepositoryTest, T-10], [HTTP-мок и ручной OAuth],
[ФР-5], [StorageSyncEngineTest], [12 сценариев синхронизации],
[ФР-6], [TaskOrchestratorTest, T-11], [Очередь и экран задач],
) <tbl-trace>
=== Рекомендации по сопровождению тестов
При изменении криптомодуля обязателен прогон `:domain:test`. При правках синхронизации `:usecases:test`. Перед релизом полный `./gradlew test` и выборочный `connectedDebugAndroidTest`. Регрессионный чек-лист T-7…T-12 выполняется после изменений в Compose-экранах vault. Скрипт `Report/scripts/gen_test_tables.py` обновляет реестр тестов в ПЗ при добавлении новых `@Test`.
== Вывод
План тестирования выполнен: автоматизированное покрытие охватывает криптографию, синхронизацию, задачи, парсинг OTP и обработку ошибок; ручные сценарии подтвердили пригодность UI для vault и OAuth. Совокупность испытаний обосновывает готовность прототипа Wallenc к демонстрации и развитию в рамках ВКР.

44
Report/includes/ch06.typ Normal file
View File

@@ -0,0 +1,44 @@
#import "common.typ": pz-table
= Оценка результатов и экономические показатели
== Обзор рынка программного обеспечения и аналогов
Рынок клиентских решений для защищённого хранения данных представлен продуктами классов «локальный сейф», менеджеры секретов и zero-knowledge файловые клиенты (см. табл. @tbl-analog, гл. 1). Ниша Wallenc мобильный универсальный vault без собственного backend приложения с возможностью подключения внешних провайдеров через OAuth.
== Оценка экономических затрат на разработку проекта
Затраты на разработку в рамках практики (09.02.202606.05.2026) оцениваются по трудозатратам обучающегося и использованию открытых инструментов (Android Studio, Kotlin, Typst).
#pz-table(
[Структура затрат на разработку (оценка)],
3,
table.header([Статья затрат], [Ед.], [Сумма, руб.]),
[Трудозатраты (≈480 ч практики)], [н/ч], [0 (учётная работа в рамках учебного плана)],
[Лицензии ПО (IDE, SDK)], [комплект], [0],
[Устройство для тестирования], [1], [имеется у исполнителя],
[Итого прямых денежных затрат], [], [ 0],
) <tbl-cost>
#pz-table(
[Календарный план-график практики (фрагмент)],
2,
table.header([Период], [Результат]),
[09.0228.02.2026], [Анализ, аналоги, ТЗ, архитектура],
[01.0328.03.2026], [Проектирование БД, стек, OAuth-исследования],
[29.0319.04.2026], [Реализация ядра, UI, Room, Яндекс],
[20.0405.05.2026], [Тестирование, иллюстрации, отчётность],
[06.05.2026], [Защита практики, итоговая сборка ПЗ],
) <tbl-calendar>
Календарный график работ совпадает с дневником практики: этап 1 аналитика и проектирование (февраль–март 2026); этап 2 реализация (29.0319.04.2026); оформление документации апрель–май 2026.
== Модель применения и окупаемости
*Сценарий применения* внедрение в ООО НМФ «Нейротех» как внутренний инструмент защищённого хранения файлов сотрудников на корпоративных или личных облачных аккаунтах при сохранении zero-knowledge модели.
*Эффект*: снижение риска утечки при компрометации провайдера; отсутствие затрат на собственный сервер приложения. Окупаемость для учебно-промышленного прототипа выражается не в прямой прибыли, а в сокращении рисков и возможности доработки продукта без смены архитектуры.
== Вывод
Экономическая оценка показывает низкие прямые затраты на создание прототипа и потенциальную практическую ценность для организации-партнёра. Детальный рыночный анализ приведён в п. 1.3.5; полноценный раздел ТЭО в объёме отраслевых ВКР не требуется согласно рекомендациям кафедры.

View File

@@ -0,0 +1,39 @@
// Таблицы — как в «Пример работы с Typst.typ» и gost: figure + table + table.header.
// Разрыв длинных таблиц и подпись сверху задаёт шаблон modern-g7-32 (style.typ).
#show table: set text(hyphenate: true)
#let pz-table(caption, columns, ..body) = figure(
table(
columns: columns,
..body,
),
caption: caption,
)
// Реестры unit-тестов: перенос длинных идентификаторов (camelCase, _); сетка как в примере Typst.
#let pz-test-table(caption, columns, ..body) = figure(
{
show table: set text(hyphenate: true, size: 9pt)
show table.cell: cell => {
show regex("[a-z0-9][A-Z]"): m => {
m.text.first() + sym.zws + m.text.last()
}
show regex("[_]"): it => it + sym.zws
cell
}
table(
columns: columns,
..body,
)
},
caption: caption,
)
#let pz-fig(path, caption, lbl) = [
#figure(
image("../images/" + path, width: 100%),
caption: caption,
)
#label(lbl)
]

View File

@@ -0,0 +1,15 @@
#heading(numbering: none, outlined: true)[Заключение]
В пояснительной записке рассмотрены анализ предметной области, проектирование и реализация мобильного приложения Wallenc клиентского кошелька для безопасного хранения данных на недоверенных хранилищах без собственного сервера.
По главе 1 сформированы требования и выполнен сравнительный анализ аналогов; обоснован выбор стека Kotlin/Compose/Room/Hilt. По главе 2 спроектированы бизнес-процессы, DFD, UML-диаграммы и модель данных Room. Глава 3 описывает пользовательские сценарии и интерфейсные решения. Глава 4 представляет реализованные модули и отсылает к полному листингу в приложении А. Глава 5 документирует план и результаты тестирования. Глава 6 содержит краткую экономическую оценку.
*Цель работы достигнута*: разработан и протестирован прототип Android-приложения с клиентским шифрованием, управлением vault, OAuth Яндекс и проектным контуром синхронизации.
*Перспективы развития*: завершение синхронизации по модели коммитов; поддержка дополнительных провайдеров; расширение автоматизированных UI-тестов; оформление акта внедрения (приложение Д при наличии).
Программная документация (ТЗ, руководство пользователя, материалы испытаний) приведена в приложении Б; иллюстрации интерфейса в приложении В; диаграммы в приложении Г.
По тестированию подтверждено: 68 модульных unit-тестов в `src/test` (модули `:domain`, `:domain-vault`, `:usecases`, `:ui`, `:task-runtime`), инструментальные тесты Compose и Room, ручной протокол из двенадцати сценариев. Отчёты Gradle (рис. 2731) и чек-лист UI (рис. 32) включены в гл. 5.
Дальнейшие шаги: завершение синхронизации с облаком; расширение androidTest для OAuth без ручного ввода; публикация актуальных скриншотов вместо учебных заглушек; подготовка акта внедрения (прил. Д) при эксплуатации в Нейротех.

32
Report/includes/intro.typ Normal file
View File

@@ -0,0 +1,32 @@
#heading(numbering: none, outlined: true)[Введение]
Современные пользователи хранят личные и рабочие данные в облачных сервисах и на съёмных носителях, однако инфраструктура провайдера не всегда может считаться доверенной. Утечки, компрометация учётных записей и юрисдикционные риски делают актуальным подход, при котором конфиденциальность обеспечивается на стороне клиента до размещения данных во внешнем хранилище @nist-aes @clean-arch.
*Актуальность* темы обусловлена распространением мобильных приложений для хранения файлов и секретов, а также ограниченностью готовых решений: многие продукты привязаны к собственному backend, закрытой экосистеме или узкой предметной области (менеджеры паролей, локальные «сейфы») @google-secure-folder @bitwarden-help @cryptomator-docs.
*Цель работы* повысить конфиденциальность пользовательских данных при работе с недоверенными хранилищами за счёт разработки мобильного клиентского приложения Wallenc, не требующего развёртывания собственного сервера приложения и обеспечивающего единую модель vault с клиентским шифрованием.
Для достижения цели поставлены следующие *задачи*:
+ выполнить анализ предметной области и сравнительный обзор аналогов, сформировать требования к программному продукту;
+ спроектировать архитектуру системы, модель данных и пользовательские сценарии;
+ реализовать программные модули приложения Wallenc на платформе Android (Kotlin);
+ провести тестирование программного обеспечения и оформить программную документацию;
+ выполнить оценку экономических затрат и перспектив применения результатов.
*Объект исследования* методы и средства клиентской защиты данных в мобильных приложениях. *Предмет исследования* проектные и программные решения приложения Wallenc.
*Методы исследования*: анализ нормативной и технической документации, сравнительный анализ программных аналогов, объектно-ориентированное проектирование (UML, BPMN, DFD), прототипирование пользовательского интерфейса, программная реализация и тестирование @gost7322017 @kotlin-docs.
*Практическая база.* Работа выполнена в рамках производственной (технологической) практики в ООО НМФ «Нейротех» (09.02.202606.05.2026) по направлению 09.03.04 «Программная инженерия», профиль «Методы и средства разработки программного обеспечения». Научный руководитель от университета Беликов А. Н.; руководитель от организации Алексеев Д. М.
*Научная новизна* заключается в сочетании универсальной модели vault, клиентского шифрования и адаптерного доступа к разным типам хранилищ без собственного сервера приложения, с проектным контуром синхронизации зашифрованных данных без передачи ключей провайдеру.
*Практическая значимость*: результаты могут использоваться при дальнейшей разработке продукта в ООО НМФ «Нейротех» и в учебных проектах по мобильной разработке и информационной безопасности. *Апробация* прохождение производственной практики (09.02.202606.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. *Приложение Г* диаграммы. На приложения даны ссылки в гл. 45 и в настоящем введении.

1
Report/listings/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
generated/

View File

@@ -0,0 +1,65 @@
repo_root: ".."
report_root: "."
scan_root: ".."
include_extensions:
.kt: kotlin
.kts: kotlin
.xml: xml
.pro: text
.properties: text
.toml: toml
include_filenames:
gradlew: bash
exclude_dirs:
- build
- .gradle
- .idea
- Report
- captures
- .cxx
- app/release
- node_modules
exclude_globs:
- "**/generated/**"
- "**/*.jar"
- "**/*.apk"
- "**/*.jks"
- "**/*.keystore"
- "**/*.hprof"
- local.properties
- google-services.json
- "**/*.png"
- "**/*.webp"
- "**/*.jpg"
- "**/*.gif"
- "**/*.md"
- "**/*.puml"
- "**/*.gaphor"
- "**/*.pdf"
- "**/*.typ"
modules_order:
- root
- app
- domain
- usecases
- ui
- domain-vault
- infrastructure-android
- vault-contracts
- task-runtime
root_build_files:
- settings.gradle.kts
- build.gradle.kts
- gradle.properties
- gradle/libs.versions.toml
- gradle/wrapper/gradle-wrapper.properties
- gradlew
pagebreak_per_module: false
pagebreak_per_file: false

94
Report/references.bib Normal file
View File

@@ -0,0 +1,94 @@
@misc{gost7322017,
title = {ГОСТ 7.32—2017. Отчёт о научно-исследовательской работе. Структура и правила оформления},
year = {2017},
note = {Электронный ресурс: https://tsu.ru/upload/medialibrary/235/gost_7.32_2017.pdf},
}
@misc{kotlin-docs,
title = {Kotlin Documentation},
author = {{JetBrains}},
year = {2026},
url = {https://kotlinlang.org/docs/home.html},
}
@misc{android-arch,
title = {Guide to app architecture},
author = {{Google}},
year = {2026},
url = {https://developer.android.com/topic/architecture},
}
@misc{compose-docs,
title = {Get started with Jetpack Compose},
author = {{Google}},
year = {2026},
url = {https://developer.android.com/develop/ui/compose/documentation},
}
@misc{room-docs,
title = {Save data in a local database using Room},
author = {{Google}},
year = {2026},
url = {https://developer.android.com/training/data-storage/room},
}
@misc{hilt-docs,
title = {Hilt},
author = {{Google}},
year = {2026},
url = {https://developer.android.com/training/dependency-injection/hilt-android},
}
@misc{oauth-rfc6749,
title = {The OAuth 2.0 Authorization Framework},
author = {Hardt, D.},
year = {2012},
url = {https://datatracker.ietf.org/doc/html/rfc6749},
}
@misc{yandex-oauth,
title = {OAuth для сервисов Яндекса},
author = {{Яндекс}},
year = {2026},
url = {https://yandex.ru/dev/id/doc/ru/},
}
@misc{nist-aes,
title = {Advanced Encryption Standard (AES)},
author = {{NIST}},
year = {2001},
url = {https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf},
}
@misc{clean-arch,
title = {The Clean Architecture},
author = {Martin, R. C.},
year = {2012},
url = {https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html},
}
@misc{cryptomator-docs,
title = {Cryptomator Documentation},
year = {2026},
url = {https://docs.cryptomator.org/en/latest/},
}
@misc{bitwarden-help,
title = {Bitwarden Help Center},
year = {2026},
url = {https://bitwarden.com/help/},
}
@misc{google-secure-folder,
title = {Use a PIN to lock your Safe folder in Files by Google},
author = {{Google}},
year = {2026},
url = {https://support.google.com/files/answer/9935263},
}
@misc{wallenc-repo,
title = {Исходный код проекта Wallenc},
author = {Пытков, Р. Е.},
year = {2026},
note = {Приватный репозиторий Gitea ЮФУ; доступ для проверки по запросу руководителя ВКР},
}

7
Report/scripts/build-pz.sh Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/.."
python3 scripts/gen_listings.py
python3 scripts/gen_test_tables.py
python3 scripts/check_images.py
typst compile --root .. ояснительная_записка_ПытковРЕ.typ"

View File

@@ -0,0 +1,51 @@
#!/usr/bin/env python3
"""Verify IMAGES_REGISTRY.md entries exist on disk and match .typ references."""
from __future__ import annotations
import re
import sys
from pathlib import Path
def main() -> int:
report = Path(__file__).resolve().parent.parent
repo = report.parent
images = report / "images"
registry = images / "IMAGES_REGISTRY.md"
typ = report / "Пояснительная_записка_ПытковРЕ.typ"
errors = 0
if not registry.exists():
print("Missing IMAGES_REGISTRY.md", file=sys.stderr)
return 1
rows = re.findall(
r"\|\s*\d+\s*\|\s*([^\|]+?)\s*\|\s*(\w+)\s*\|",
registry.read_text(encoding="utf-8"),
)
rows = [(f.strip(), s.strip()) for f, s in rows if f.startswith("fig_")]
for fname, status in rows:
p = images / fname
if not p.exists():
print(f"MISSING file: {fname} (status {status})", file=sys.stderr)
errors += 1
elif status == "ready" and p.stat().st_size < 1000:
print(f"WARN small file: {fname}", file=sys.stderr)
if typ.exists():
text = typ.read_text(encoding="utf-8")
for m in re.finditer(r'image\("(?:Report/)?images/([^"]+)"', text):
f = images / m.group(1)
if not f.exists():
print(f"MISSING in typ: images/{m.group(1)}", file=sys.stderr)
errors += 1
if errors:
print(f"check_images: {errors} error(s)", file=sys.stderr)
return 1
print("check_images: OK", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,196 @@
#!/usr/bin/env python3
"""Generate Typst listing wrappers for Appendix A from Wallenc source tree."""
from __future__ import annotations
import argparse
import hashlib
import sys
from pathlib import Path
try:
import yaml
except ImportError:
yaml = None # type: ignore
def load_config(path: Path) -> dict:
text = path.read_text(encoding="utf-8")
if yaml is None:
raise SystemExit("PyYAML required: pip install pyyaml")
return yaml.safe_load(text)
def path_hash(rel: str) -> str:
return hashlib.sha256(rel.encode()).hexdigest()[:8]
def lang_for(path: Path, ext_map: dict, name_map: dict) -> str:
if path.name in name_map:
return name_map[path.name]
return ext_map.get(path.suffix.lower(), "text")
def should_skip(path: Path, parts: tuple[str, ...], cfg: dict) -> bool:
exclude_dirs = set(cfg.get("exclude_dirs", []))
if any(p in exclude_dirs for p in parts):
return True
s = path.as_posix()
for g in cfg.get("exclude_globs", []):
g = g.replace("**/", "")
if g.startswith("**/"):
g = g[3:]
if g.endswith("/**"):
if g[:-3] in parts:
return True
elif g.startswith("*."):
if s.endswith(g[1:]):
return True
elif g in s:
return True
return False
def collect_files(scan_root: Path, cfg: dict) -> list[Path]:
ext_map: dict = cfg.get("include_extensions", {})
name_map: dict = cfg.get("include_filenames", {})
allowed_suffixes = set(ext_map.keys())
allowed_names = set(name_map.keys())
files: list[Path] = []
for p in scan_root.rglob("*"):
if not p.is_file():
continue
rel_parts = p.relative_to(scan_root).parts
if should_skip(p, rel_parts, cfg):
continue
if p.name in allowed_names or p.suffix.lower() in allowed_suffixes:
files.append(p)
return sorted(files, key=lambda x: x.as_posix())
def module_key(rel: Path, cfg: dict) -> str:
order = cfg.get("modules_order", [])
parts = rel.parts
if rel.name in {Path(f).name for f in cfg.get("root_build_files", [])}:
return "root"
if parts and parts[0] in order:
return parts[0]
return "other"
def group_files(files: list[Path], scan_root: Path, cfg: dict) -> dict[str, list[Path]]:
groups: dict[str, list[Path]] = {}
root_files = {Path(f).name for f in cfg.get("root_build_files", [])}
for f in files:
rel = f.relative_to(scan_root)
if rel.name in root_files or len(rel.parts) == 1 and rel.suffix in {".kts", ".properties"}:
groups.setdefault("root", []).append(f)
continue
key = module_key(rel, cfg)
groups.setdefault(key, []).append(f)
order = cfg.get("modules_order", []) + ["other"]
return {k: groups[k] for k in order if k in groups}
def typst_escape_path(rel: str) -> str:
return rel.replace("\\", "/")
def write_listing(
out_path: Path,
rel_from_report: str,
caption: str,
label: str,
lang: str,
) -> None:
rel_typ = typst_escape_path(rel_from_report)
# Подпись перед кодом: figure.caption(position: top) в теле (placement: top — про float, не порядок).
content = (
f'#let lst-body-{label} = read("{rel_typ}")\n'
f"#figure(\n"
f" [\n"
f" #figure.caption(position: top)[{caption}]\n"
f" #block(breakable: true)[\n"
f" #raw(lst-body-{label}, lang: \"{lang}\", block: true)\n"
f" ]\n"
f" ],\n"
f" supplement: [Листинг],\n"
f" gap: 0.4em,\n"
f") <lst-{label}>\n\n"
)
out_path.write_text(content, encoding="utf-8")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--dry-run", action="store_true")
parser.add_argument("--config", default=None)
args = parser.parse_args()
script_dir = Path(__file__).resolve().parent
report_dir = script_dir.parent
config_path = Path(args.config) if args.config else report_dir / "listings" / "listings.config.yaml"
cfg = load_config(config_path)
scan_root = (report_dir / cfg.get("scan_root", "..")).resolve()
generated = report_dir / "listings" / "generated"
ext_map: dict = cfg.get("include_extensions", {})
name_map: dict = cfg.get("include_filenames", {})
files = collect_files(scan_root, cfg)
if args.dry_run:
for f in files:
print(f.relative_to(scan_root).as_posix())
print(f"Total: {len(files)}", file=sys.stderr)
return 0
generated.mkdir(parents=True, exist_ok=True)
groups = group_files(files, scan_root, cfg)
listing_paths: list[str] = []
module_includes: list[str] = []
for mod, mod_files in groups.items():
mod_listings: list[str] = []
for f in mod_files:
rel_repo = f.relative_to(scan_root).as_posix()
# Paths relative to listings/generated/*.typ (typst compile --root ..)
rel_report = Path("../../..") / rel_repo
rel_report_str = rel_report.as_posix()
hid = path_hash(rel_repo)
label = hid
cap = f"Исходный файл `{rel_repo}`"
lang = lang_for(f, ext_map, name_map)
listing_name = f"listing-{hid}.typ"
listing_path = generated / listing_name
write_listing(listing_path, rel_report_str, cap, label, lang)
mod_listings.append(listing_name)
listing_paths.append(listing_name)
mod_file = generated / f"module-{mod}.typ"
with mod_file.open("w", encoding="utf-8") as mf:
title = "Система сборки" if mod == "root" else f"Модуль :{mod}"
mf.write(f"== {title}\n\n")
if cfg.get("pagebreak_per_module") and mod != "root":
mf.write("#pagebreak(weak: true)\n\n")
for ln in mod_listings:
mf.write(f'#include "{ln}"\n')
module_includes.append(f"module-{mod}.typ")
index = generated / "_index.typ"
with index.open("w", encoding="utf-8") as ix:
ix.write(f"// Generated listings: {len(files)} files\n\n")
appendix = generated / "appendix-a.typ"
with appendix.open("w", encoding="utf-8") as ap:
ap.write("// AUTO-GENERATED by gen_listings.py — do not edit\n\n")
ap.write("#set figure(gap: 0.4em)\n\n")
for mi in module_includes:
ap.write(f'#include "{mi}"\n')
print(f"Generated {len(files)} listings in {generated}", file=sys.stderr)
return 0
if __name__ == "__main__":
raise SystemExit(main())

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Generate Typst tables of unit tests for chapter 5."""
from __future__ import annotations
import re
from collections import defaultdict
from pathlib import Path
def typst_escape(s: str) -> str:
return s.replace("\\", "\\\\").replace("`", "\\`")
def emit_table(
lines: list[str],
caption: str,
ncol: int,
headers: list[str],
data_rows: list[list[str]],
label: str,
) -> None:
lines.append("#pz-test-table(\n")
lines.append(f" [{caption}],\n")
lines.append(f" {ncol},\n")
lines.append(" table.header(\n")
for h in headers:
lines.append(f" [{typst_escape(h)}],\n")
lines.append(" ),\n")
for row in data_rows:
cells = ", ".join(f"[{typst_escape(c)}]" for c in row)
lines.append(f" {cells},\n")
lines.append(f") <{label}>\n\n")
def main() -> None:
root = Path(__file__).resolve().parents[2]
out = Path(__file__).resolve().parents[1] / "includes" / "ch05-tests-generated.typ"
rows: list[tuple[str, str, str, str]] = []
for p in sorted(root.rglob("*.kt")):
if "/src/test/" not in p.as_posix():
continue
text = p.read_text(encoding="utf-8", errors="replace")
cls_m = re.search(r"class\s+(\w+)", text)
if not cls_m:
continue
cls = cls_m.group(1)
mod = p.parts[p.parts.index("src") - 1]
rel = p.relative_to(root).as_posix()
for m in re.finditer(r"@Test[\s\S]*?fun\s+(?:`([^`]+)`|(\w+))\s*\(", text):
name = m.group(1) or m.group(2)
rows.append((mod, cls, name, rel))
by_mod: dict[str, list] = defaultdict(list)
for mod, cls, name, rel in rows:
by_mod[mod].append((cls, name, rel))
lines = [
"// AUTO-GENERATED by gen_test_tables.py — include from ch05.typ\n",
'#import "common.typ": pz-test-table\n\n',
]
summary_rows = []
for mod in sorted(by_mod):
for cls, name, rel in by_mod[mod]:
short = rel.split("/")[-1]
summary_rows.append([mod, cls, name, short])
emit_table(
lines,
"Сводка модульных unit-тестов (src/test)",
4,
["Модуль", "Класс", "Метод", "Файл"],
summary_rows,
"tbl-unit-all",
)
for mod in sorted(by_mod):
safe = mod.replace("-", "_")
lines.append(f"=== Реестр тестов модуля :{mod}\n\n")
mod_rows = [
[cls, name, rel.split("/")[-1]]
for cls, name, rel in by_mod[mod]
]
emit_table(
lines,
f"Unit-тесты модуля :{mod}",
3,
["Класс", "Метод", "Файл"],
mod_rows,
f"tbl-unit-{safe}",
)
out.write_text("".join(lines), encoding="utf-8")
print(f"Wrote {out} ({len(rows)} tests)", flush=True)
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View File

@@ -0,0 +1,387 @@
@startuml
scale 2
skinparam shadowing false
skinparam classFontSize 11
' PNG (лимит растра по умолчанию 4096): PLANTUML_LIMIT_SIZE=8192 java -Xmx2g -jar plantuml.jar -charset UTF-8 -tpng …
package usecases {
class ManageStoragesEncryptionUseCase {
+ enableEncryption(IStorageInfo, EncryptKey, boolean): Unit
+ openStorage(IStorageInfo, EncryptKey, boolean): Unit
+ clearAndDisableEncryption(IStorageInfo): Unit
+ closeStorage(IStorageInfo): Unit
+ canEncrypt(IStorageInfo): Unit
+ changePassword(IStorageInfo, EncryptKey, boolean): Unit
}
class RemoveStorageUseCase {
+ remove(IStorageInfo): Unit
}
class GetOpenedStoragesUseCase {
+ getOpenedStorages(): StateFlow<Map<UUID, IStorageInfo>>
}
class ManageLocalVaultUseCase {
+ getLocalStorages(): StateFlow<List<IStorageInfo>>
+ createStorage(): Unit
}
class StorageFileManagementUseCase {
+ getAllDirs(): Unit
+ setStorage(IStorageInfo): void
+ getAllFiles(): Unit
}
class RenameStorageUseCase {
+ rename(IStorageInfo, String): Unit
}
}
package tasks {
class TaskLogLine {
+ getTimestampMs(): long
+ getLevel(): TaskLogLevel
+ getMessage(): String
}
class PipelineWork {
+ run(TaskContext): Unit
}
class TaskContext {
+ reportProgress(Float, String): Unit
+ reportProgress(TaskProgress): Unit
+ log(TaskLogLevel, String): void
+ getTaskId(): TaskId
}
class PipelineState {
+ getTasks(): List
+ getRunningTaskIds(): Set
}
class ITaskOrchestrator {
+ getLogLines(): StateFlow<List<TaskLogLine>>
+ enqueue(String, PipelineWork): TaskId
+ getPipelineState(): StateFlow<PipelineState>
+ getForegroundUi(): StateFlow<TaskForegroundUiState>
+ cancel(TaskId): boolean
+ cancelAll(): void
}
class TaskProgress {
+ getLabel(): String
+ getFraction(): Float
}
class TaskForegroundUiState {
}
class TaskForegroundItem {
+ getProgress(): TaskProgress
+ getTitle(): String
+ getTaskId(): TaskId
}
class TaskLogLevel {
+ Debug
+ Warn
+ Error
+ Info
+ values(): TaskLogLevel[]
+ valueOf(String): TaskLogLevel
+ getEntries(): EnumEntries<TaskLogLevel>
}
class TaskRunState {
}
class TaskId {
+ getUuid(): UUID
}
class PipelineTask {
+ getId(): TaskId
+ getDispatcher(): CoroutineDispatcher
+ getState(): TaskRunState
+ getTitle(): String
}
}
package interfaces {
class ILogger {
+ debug(String, String): void
}
class IYandexVault {
+ getAccountEmail(): String
}
class IMetaInfo {
+ getSize(): long
+ isDeleted(): boolean
+ isHidden(): boolean
+ getLastModified(): Instant
+ getPath(): String
}
class IStorage {
+ rename(String): Unit
+ setEncInfo(StorageEncryptionInfo): Unit
+ isEmpty(): Flow<Boolean>
+ getUuid(): UUID
+ getAccessor(): IStorageAccessor
+ isAvailable(): StateFlow<Boolean>
+ getSize(): StateFlow<Long>
+ getNumberOfFiles(): StateFlow<Integer>
+ clearAllContent(): Unit
+ getMetaInfo(): StateFlow<IStorageMetaInfo>
+ isVirtualStorage(): boolean
}
class IVaultsManager {
+ getLocalVault(): IVault
+ removeRemoteVault(UUID): Unit
+ addYandexVault(String): Unit
+ getRemoteVaults(): StateFlow<List<IVault>>
+ getAllStorages(): StateFlow<List<IStorage>>
+ getAllVaults(): StateFlow<List<IVault>>
+ getUnlockManager(): IUnlockManager
}
class IStorageMetaInfo {
+ getEncInfo(): StorageEncryptionInfo
+ getName(): String
+ getLastModified(): Instant
}
class IFile {
+ getMetaInfo(): IMetaInfo
}
class IStorageInfo {
+ isEmpty(): Flow<Boolean>
+ getUuid(): UUID
+ isAvailable(): StateFlow<Boolean>
+ getSize(): StateFlow<Long>
+ getNumberOfFiles(): StateFlow<Integer>
+ getMetaInfo(): StateFlow<IStorageMetaInfo>
+ isVirtualStorage(): boolean
}
class IStorageExplorer {
+ getCurrentPath(): StateFlow<String>
}
class IVaultInfo {
+ getAvailableSpace(): StateFlow<Integer>
+ getType(): VaultType
+ getTotalSpace(): StateFlow<Integer>
+ getUuid(): UUID
+ isAvailable(): StateFlow<Boolean>
+ getStorages(): StateFlow<List<IStorageInfo>>
}
class IUnlockManager {
+ close(IStorage): Unit
+ close(UUID): Unit
+ open(IStorage, EncryptKey, boolean): Unit
+ getOpenedStorages(): StateFlow<Map<UUID, IStorage>>
}
class IDirectory {
+ getMetaInfo(): IMetaInfo
+ getElementsCount(): Integer
}
class IStorageAccessor {
+ getFilesFlow(String): Flow<DataPackage<List<IFile>>>
+ getDirs(String): Unit
+ getDirsUpdates(): SharedFlow<DataPackage<List<IDirectory>>>
+ touchDir(String): Unit
+ getFiles(String): Unit
+ getSize(): StateFlow<Long>
+ isAvailable(): StateFlow<Boolean>
+ getDirInfo(String): Unit
+ openRead(String): Unit
+ moveToTrash(String): Unit
+ delete(String): Unit
+ touchFile(String): Unit
+ getAllDirs(): Unit
+ openWrite(String): Unit
+ getAllFiles(): Unit
+ getDirsFlow(String): Flow<DataPackage<List<IDirectory>>>
+ getNumberOfFiles(): StateFlow<Integer>
+ getFilesUpdates(): SharedFlow<DataPackage<List<IFile>>>
+ setHidden(String, boolean): Unit
+ getFileInfo(String): Unit
}
class IVault {
+ getAvailableSpace(): StateFlow<Integer>
+ getType(): VaultType
+ getTotalSpace(): StateFlow<Integer>
+ remove(IStorage): Unit
+ createStorage(StorageEncryptionInfo): Unit
+ getUuid(): UUID
+ isAvailable(): StateFlow<Boolean>
+ getStorages(): StateFlow<List<IStorage>>
+ createStorage(): Unit
}
}
package encrypt {
class EncryptorWithStaticIv {
+ decryptBytesbyte[](byte[])
+ encryptStream(java.io.OutputStream): java.io.OutputStream
+ decryptStream(java.io.InputStream): java.io.InputStream
+ encryptBytesbyte[](byte[])
+ dispose(): void
+ encryptString(String): String
+ decryptString(String): String
}
class Encryptor {
+ AES_SETTINGS: String
+ IV_LEN: int
+ decryptBytesbyte[](byte[])
+ encryptStream(java.io.OutputStream): java.io.OutputStream
+ decryptStream(java.io.InputStream): java.io.InputStream
+ encryptBytesbyte[](byte[])
+ dispose(): void
+ encryptString(String): String
+ decryptString(String): String
}
}
package datatypes {
class Tree {
+ getValue(): Unit
+ getChildren(): List
+ setChildren(List): void
}
class EncryptKey {
+ toAesKey(): SecretKeySpec
+ getBytes(): byte[]
}
class DataPackage {
+ getData(): Unit
+ isError(): Boolean
+ isLoading(): Boolean
}
class DataPage {
+ getPageLength(): int
+ getPageIndex(): int
+ getHasNext(): Boolean
}
class StorageEncryptionInfo {
+ getEncryptedTestData(): String
+ getPathIv(): byte[]
}
}
package common.impl {
class CommonDirectory {
+ getElementsCount(): Integer
+ getMetaInfo(): CommonMetaInfo
}
class CommonStorageMetaInfo {
+ getEncInfo(): StorageEncryptionInfo
+ getName(): String
+ getLastModified(): Instant
}
class CommonMetaInfo {
+ getSize(): long
+ getPath(): String
+ isDeleted(): boolean
+ isHidden(): boolean
+ getLastModified(): Instant
}
class CommonFile {
+ getMetaInfo(): IMetaInfo
}
}
package auth {
class RemoteYandexAuthResult {
}
class RemoteYandexSignInLauncher {
+ launch(): void
}
}
package enums {
class VaultType {
+ DECRYPTED: VaultType
+ LOCAL: VaultType
+ YANDEX: VaultType
+ valueOf(String): VaultType
+ values(): VaultType[]
+ getEntries(): EnumEntries<VaultType>
}
}
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorageMetaInfo
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorageInfo
usecases.ManageStoragesEncryptionUseCase ..> tasks.TaskProgress
usecases.ManageStoragesEncryptionUseCase ..> datatypes.EncryptKey
usecases.ManageStoragesEncryptionUseCase ..> encrypt.Encryptor
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorage
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IUnlockManager
usecases.ManageStoragesEncryptionUseCase ..> datatypes.StorageEncryptionInfo
usecases.RemoveStorageUseCase ..> usecases.ManageStoragesEncryptionUseCase
usecases.RemoveStorageUseCase ..> interfaces.IStorage
usecases.RemoveStorageUseCase ..> interfaces.IStorageInfo
usecases.RemoveStorageUseCase ..> interfaces.IVaultsManager
usecases.RemoveStorageUseCase ..> interfaces.IUnlockManager
usecases.RemoveStorageUseCase ..> interfaces.IVault
tasks.TaskLogLine ..> tasks.TaskLogLevel
tasks.PipelineWork ..> tasks.TaskContext
interfaces.IVault <|.. interfaces.IYandexVault
interfaces.IVaultInfo <|.. interfaces.IYandexVault
interfaces.IYandexVault ..> interfaces.IStorage
interfaces.IYandexVault ..> interfaces.IVault
interfaces.IYandexVault ..> enums.VaultType
interfaces.IYandexVault ..> datatypes.StorageEncryptionInfo
usecases.GetOpenedStoragesUseCase ..> interfaces.IStorageInfo
usecases.GetOpenedStoragesUseCase ..> interfaces.IUnlockManager
tasks.TaskContext ..> tasks.TaskLogLevel
tasks.TaskContext ..> tasks.TaskProgress
tasks.TaskContext ..> tasks.TaskId
usecases.ManageLocalVaultUseCase ..> interfaces.IStorageInfo
usecases.ManageLocalVaultUseCase ..> interfaces.IVaultsManager
usecases.ManageLocalVaultUseCase ..> interfaces.IVault
usecases.StorageFileManagementUseCase ..> interfaces.IFile
usecases.StorageFileManagementUseCase ..> interfaces.IStorage
usecases.StorageFileManagementUseCase ..> interfaces.IStorageInfo
usecases.StorageFileManagementUseCase ..> interfaces.IDirectory
usecases.StorageFileManagementUseCase ..> interfaces.IStorageAccessor
interfaces.IStorageInfo <|.. interfaces.IStorage
interfaces.IStorage ..> interfaces.IStorageMetaInfo
interfaces.IStorage ..> interfaces.IStorageInfo
interfaces.IStorage ..> tasks.TaskProgress
interfaces.IStorage ..> interfaces.IStorageAccessor
interfaces.IStorage ..> datatypes.StorageEncryptionInfo
interfaces.IVaultsManager ..> interfaces.IStorage
interfaces.IVaultsManager ..> interfaces.IUnlockManager
interfaces.IVaultsManager ..> interfaces.IVault
interfaces.IDirectory <|.. common.impl.CommonDirectory
common.impl.CommonDirectory ..> common.impl.CommonMetaInfo
common.impl.CommonDirectory ..> interfaces.IMetaInfo
common.impl.CommonDirectory ..> interfaces.IDirectory
tasks.PipelineState ..> tasks.TaskId
tasks.PipelineState ..> tasks.PipelineTask
interfaces.IStorageMetaInfo <|.. common.impl.CommonStorageMetaInfo
common.impl.CommonStorageMetaInfo ..> interfaces.IStorageMetaInfo
common.impl.CommonStorageMetaInfo ..> datatypes.StorageEncryptionInfo
interfaces.IMetaInfo <|.. common.impl.CommonMetaInfo
common.impl.CommonMetaInfo ..> interfaces.IMetaInfo
interfaces.IStorageMetaInfo ..> datatypes.StorageEncryptionInfo
interfaces.IFile ..> interfaces.IMetaInfo
tasks.ITaskOrchestrator ..> tasks.TaskLogLine
tasks.ITaskOrchestrator ..> tasks.PipelineState
tasks.ITaskOrchestrator ..> tasks.TaskForegroundUiState
tasks.ITaskOrchestrator ..> tasks.PipelineWork
tasks.ITaskOrchestrator ..> tasks.TaskId
interfaces.IStorageInfo ..> interfaces.IStorageMetaInfo
interfaces.IStorageInfo ..> interfaces.IStorage
usecases.RenameStorageUseCase ..> interfaces.IStorage
usecases.RenameStorageUseCase ..> interfaces.IStorageInfo
interfaces.IVaultInfo ..> interfaces.IStorageInfo
interfaces.IVaultInfo ..> interfaces.IVault
interfaces.IVaultInfo ..> enums.VaultType
tasks.TaskForegroundItem ..> tasks.TaskProgress
tasks.TaskForegroundItem ..> tasks.TaskId
interfaces.IUnlockManager ..> interfaces.IStorage
interfaces.IUnlockManager ..> datatypes.EncryptKey
interfaces.IDirectory ..> interfaces.IMetaInfo
interfaces.IStorageAccessor ..> interfaces.IFile
interfaces.IStorageAccessor ..> interfaces.IDirectory
interfaces.IStorageAccessor ..> datatypes.DataPackage
interfaces.IVaultInfo <|.. interfaces.IVault
interfaces.IVault ..> interfaces.IStorage
interfaces.IVault ..> interfaces.IVaultInfo
interfaces.IVault ..> enums.VaultType
interfaces.IVault ..> datatypes.StorageEncryptionInfo
auth.RemoteYandexSignInLauncher ..> auth.RemoteYandexAuthResult
datatypes.DataPackage <|-- datatypes.DataPage
datatypes.DataPage ..> datatypes.DataPackage
interfaces.IFile <|.. common.impl.CommonFile
common.impl.CommonFile ..> interfaces.IMetaInfo
common.impl.CommonFile ..> interfaces.IFile
tasks.PipelineTask ..> tasks.TaskRunState
tasks.PipelineTask ..> tasks.TaskId
@enduml

View File

@@ -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

View File

@@ -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

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 255 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -0,0 +1,122 @@
# Отчёт по 1-му предварительному этапу производственной практики
**Проект:** Wallenc — универсальный кошелёк для безопасного хранения данных на небезопасных хранилищах без собственного сервера.
## Введение
На первом этапе практики была выполнена аналитическая и проектная подготовка к разработке мобильного приложения Wallenc. Цель этапа — сформировать техническую основу проекта: определить предметную область, выбрать архитектурный подход, описать требования к системе и зафиксировать технологические решения для последующей реализации.
Ключевая идея проекта заключается в том, что безопасность пользовательских данных обеспечивается на стороне клиента: данные шифруются до отправки во внешнее хранилище, а расшифрование выполняется только в приложении при наличии корректного ключа. Это позволяет использовать небезопасные или недоверенные хранилища без потери конфиденциальности и без развертывания собственного серверного backend.
## Анализ предметной области
В рамках анализа предметной области рассмотрены подходы к хранению чувствительных данных в мобильных приложениях и в облачных хранилищах. Выделены основные требования к таким системам:
- конфиденциальность данных при хранении и передаче;
- отсутствие необходимости доверять инфраструктуре хранилища;
- устойчивость к компрометации удалённого провайдера;
- разделение логики хранения и логики криптографической защиты;
- удобный пользовательский сценарий: создание хранилища, шифрование, открытие, работа с содержимым.
Сформирован вывод, что для проекта Wallenc приоритетны клиентские криптографические механизмы, унифицированный доступ к разным типам хранилищ и архитектура с чётким разделением слоёв.
## Обзор аналогичных решений
### Аналоги
- **Google Files Secure Folder**
Что делает: локально прячет и защищает файлы в папке по PIN/Pattern внутри Android.
Минусы: по сути только локальный “сейф” (без нормальной кроссплатформенной синхронизации), ограниченные сценарии переноса между устройствами, не про универсальную модель vault для разных хранилищ.
- **Proton Pass / Proton Drive**
Что делает: end-to-end экосистема для паролей, заметок и файлов с облачной синхронизацией.
Минусы: привязка к экосистеме Proton, часть полезных сценариев/объёма и функций зависит от тарифа, меньше гибкости как “универсальный клиент” под разные внешние хранилища.
- **Bitwarden**
Что делает: менеджер паролей/секретов с шифрованием, синхронизацией, офлайн-доступом и self-host вариантом.
Минусы: ориентирован в первую очередь на учетные данные и секреты, а не на общий файловый vault; офлайн-сценарии ограничены (часть операций требует синхронизации/сервера).
- **Cryptomator**
Что делает: клиентское шифрование файловых vault перед хранением в облаке (zero-knowledge подход).
Минусы: фокус именно на шифровании файлов, а не на расширенной модели “кошелька” с собственной мета-логикой и UI-сценариями; есть ограничения интеграций в зависимости от платформы/сборки и провайдера.
Проведён обзор классов решений (secure-folder приложения, менеджеры секретов, облачные клиенты с zero-knowledge подходом). По результатам обзора отмечены типовые сильные и слабые стороны:
**Преимущества аналогов:**
- понятный пользовательский сценарий хранения;
- готовые механизмы шифрования файлов;
- интеграция с несколькими хранилищами.
**Ограничения аналогов:**
- зависимость от собственного backend или закрытой инфраструктуры;
- недостаточная прозрачность модели ключей;
- ограниченная переносимость между провайдерами.
Это подтвердило актуальность выбранной концепции Wallenc: безопасность без собственного сервера, с переносимой архитектурой хранилищ.
## Формирование технического задания
На основании анализа подготовлена структура ТЗ в логике ГОСТ 7.322017: определены цели, этапы работ, основные и дополнительные задачи, ожидаемые результаты и направления тестирования.
ТЗ синхронизировано с фактическим направлением проекта: приоритет на ядро системы хранения/шифрования и поэтапное расширение функциональности.
## Изучение архитектурных подходов
Для проекта принят архитектурный подход **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: определены предметная область, архитектурная модель, структура данных, технологический стек и требования к безопасности.
Подготовленные материалы позволяют переходить к реализации следующего этапа — построению функционального ядра приложения и расширению сценариев работы с хранилищами, включая синхронизацию зашифрованных данных с удалёнными провайдерами.

View File

@@ -0,0 +1,229 @@
# Отчёт по 2-му предварительному этапу производственной практики
**Проект:** Wallenc — универсальный кошелёк для безопасного хранения данных на небезопасных хранилищах без собственного сервера.
**Период выполнения этапа:** 29.03.202619.04.2026
## Введение
На втором этапе производственной практики выполнена реализация функционального ядра мобильного приложения Wallenc и ключевых пользовательских сценариев работы с локальными и удалёнными хранилищами.
Основной фокус этапа — перевод проектных решений первого этапа в рабочий код: управление vault, шифрование, хранение метаданных и ключевой информации, а также реализация интерфейсов для повседневной работы пользователя.
В рамках этапа обеспечена практическая готовность базовой версии приложения к использованию: реализованы операции создания, просмотра, переименования, удаления и защиты vault, а также подготовлена инфраструктура для дальнейшего расширения удалённых сценариев.
Практический результат этапа выражается не только в наличии отдельных модулей, но и в их совместной работе в рамках одного приложения: пользователь может создавать хранилища, включать защиту, открывать и закрывать зашифрованные представления, видеть состояние vault в интерфейсе и выполнять основные операции сопровождения. Все ключевые сценарии выполнены в рамках единой архитектуры, выбранной на первом этапе.
## Краткая характеристика выполненного этапа
За период 29.03.202619.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, готового к дальнейшему расширению функциональности удалённой работы с хранилищами и развитию синхронизационных механизмов.
В целом второй этап можно считать успешно завершённым: ключевые задачи реализованы, протестированы и представлены в форме, пригодной для демонстрации и последующего развития проекта на следующем этапе практики.

View File

@@ -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.322017; приоритет ядра хранения и шифрования. |
| 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.322017** (цели, этапы, основные и дополнительные задачи, ожидаемые результаты, направления тестирования), синхронизация ТЗ с приоритетом ядра хранения/шифрования. Изучение и закрепление выбора **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.202619.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.23.7); подготовка диаграмм user flow и доменной модели (заготовки в п. 5 приложения). Систематизация результатов практики, оформление отчётной документации. |
| **06.05.2026** | Завершение практики, защита результатов *(дата окончания по приказу)*. |
---
## 4. Анализ проведённой работы в период прохождения практики обучающимся
| № п/п | Выполненные мероприятия | Анализ проведённой работы |
| --- | --- | --- |
| 1 | Анализ предметной области | Выполнен систематический обзор требований к системам хранения чувствительных данных: учтены конфиденциальность, модель «недоверенное хранилище», разделение криптографии и хранения, пользовательские сценарии. Сформирован вывод о необходимости клиентского шифрования до выгрузки данных и единой абстракции над разными провайдерами — это определило дальнейшую архитектуру Wallenc. |
| 2 | Обзор аналогов | По каждому из рассмотренных решений зафиксированы назначение и ограничения (локальный сейф, привязка к экосистеме, ориентация на секреты вместо файлового vault, ограничения интеграций). Сравнение по классам продуктов позволило отделить идею Wallenc от «ещё одного облачного клиента» и обосновать фокус на переносимости и отсутствии собственного backend. |
| 3 | Техническое задание | Структура ТЗ по ГОСТ 7.322017 обеспечила связность целей, этапов и критериев тестирования. Приоритет ядра хранения и шифрования зафиксирован в ТЗ и затем последовательно реализован на втором этапе без смещения фокуса на второстепенные функции. |
| 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.322017** с приоритетом ядра хранения и шифрования.
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.322017**: цели, этапы работ, основные и дополнительные задачи, ожидаемые результаты, направления тестирования. ТЗ **синхронизировано** с направлением проекта: приоритет на ядро системы хранения и шифрования и **поэтапное** расширение функциональности.
### 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.202619.04.2026.
На втором этапе выполнена **реализация функционального ядра** Wallenc и ключевых пользовательских сценариев для локальных и удалённых хранилищ. Основной фокус — **перевод проектных решений первого этапа в рабочий код**: управление vault, шифрование, метаданные и ключевая информация, интерфейсы для повседневной работы пользователя.
Обеспечена **практическая готовность** базовой версии: операции создания, просмотра, переименования, удаления и защиты vault; инфраструктура для расширения удалённых сценариев. Модули работают **совместно** в одном приложении: пользователь создаёт хранилища, включает защиту, открывает и закрывает зашифрованные представления, видит состояние vault в интерфейсе, выполняет операции сопровождения. Сценарии реализованы в рамках **единой архитектуры**, выбранной на первом этапе.
### 3.1. Краткая характеристика выполненного этапа
За период **29.03.202619.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.23.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 и фоновая синхронизация по таймеру](<puml/wallenc_01_start_and_sync.png>)
### 5.2. Жизненный цикл vault и постановка в очередь синхронизации
![Диаграмма 2 — жизненный цикл vault и коммиты/очередь UUID](<puml/wallenc_02_vault_lifecycle.png>)
### 5.3. Навигация с главного экрана и независимый SyncWorker
![Диаграмма 3 — главный экран и фоновый SyncWorker](<puml/wallenc_03_navigation_hub.png>)
### 5.4. Диаграмма классов модуля `domain`
Сгенерирована из **скомпилированных** `.class` (Kotlin → JVM) плагином **code-atlas** (`./gradlew :domain:generateDiagrams`), исходник PlantUML: `puml/domain-classdiagram.puml`.
![Диаграмма классов domain](<puml/domain-classdiagram.png>)
---
## Заключение
По результатам **первого** этапа сформирована проектная база Wallenc: предметная область, обзор аналогов, ТЗ по ГОСТ 7.322017, архитектура 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).
---
*Конец отчёта.*

View File

@@ -0,0 +1,98 @@
#import "@preview/modern-g7-32:0.2.0": abstract, appendix-heading, appendixes, enum-numbering, gost
#set enum(numbering: enum-numbering)
#set heading(numbering: "1.1.1.1")
#show: gost.with(
ministry: "Министерство науки и высшего образования Российской Федерации",
organization: (
full: "Федеральное государственное автономное образовательное учреждение высшего образования «Южный федеральный университет»",
short: "ЮФУ",
),
about: "пояснительной записке",
subject: "разработке мобильного приложения Wallenc — универсального кошелька для безопасного хранения данных на небезопасных хранилищах без собственного сервера",
approved-by: (
name: "Беликов А. Н.",
position: "старший преподаватель кафедры системного анализа и телекоммуникаций",
),
manager: (
name: "Беликов А. Н.",
position: "научный руководитель ВКР, старший преподаватель",
),
city: "Таганрог",
year: 2026,
performers: (
(name: "Пытков Р. Е.", position: "студент гр. КТбо4-9"),
),
)
#import "includes/common.typ": pz-fig
#abstract(
"мобильное приложение",
"клиентское шифрование",
"Android",
"vault",
"zero-knowledge",
"синхронизация",
)[
Пояснительная записка посвящена разработке мобильного приложения Wallenc клиентского кошелька для безопасного хранения данных на недоверенных хранилищах без собственного сервера приложения. Описаны анализ предметной области и аналогов, формирование требований, проектирование архитектуры и пользовательского интерфейса, программная реализация на Kotlin (Android, Jetpack Compose, Room, Hilt), тестирование и краткая экономическая оценка. Реализованы управление локальными и удалёнными vault, клиентское AES-шифрование, OAuth Яндекс, служебное хранение метаданных в Room; спроектирован контур синхронизации без передачи ключей провайдеру. Исходный код размещён в приватном репозитории Gitea ЮФУ. Полный листинг исходных файлов приложение А; программная документация приложение Б.
]
#include "includes/abstract-en.typ"
#outline()
#include "includes/intro.typ"
#include "includes/ch01.typ"
#include "includes/ch02.typ"
#include "includes/ch03.typ"
#include "includes/ch04.typ"
#include "includes/ch05.typ"
#include "includes/ch06.typ"
#include "includes/conclusion.typ"
#include "includes/abbreviations.typ"
// Заголовок списка источников задаётся в gost (bibliography.title).
#bibliography("references.bib")
#show: appendixes
#appendix-heading("обязательное", level: 1)[Приложение А. Листинги исходного кода проекта Wallenc]
Полный листинг файлов, необходимых для сборки проекта (307 файлов), сформирован автоматически скриптом `Report/scripts/gen_listings.py` по конфигурации `Report/listings/listings.config.yaml`. Исключены каталоги `build/`, `**/generated/**` и бинарные артефакты.
#include "listings/generated/appendix-a.typ"
#appendix-heading("обязательное", level: 1)[Приложение Б. Программная документация]
#include "appendices/appendix-b.typ"
#appendix-heading("обязательное", level: 1)[Приложение В. Скриншоты пользовательского интерфейса]
#pz-fig("fig_05_local_vaults.jpg", [Локальные vault], "fig-05-app")
#pz-fig("fig_06_encrypt_dialog.jpg", [Диалог шифрования], "fig-06-app")
#pz-fig("fig_07_open_close_dialog.jpg", [Открытие и закрытие vault], "fig-07-app")
#pz-fig("fig_08_rename_delete_dialog.jpg", [Переименование и удаление], "fig-08-app")
#pz-fig("fig_09_remote_vaults.jpg", [Удалённые vault], "fig-09-app")
#pz-fig("fig_10_yandex_oauth.jpg", [OAuth Яндекс], "fig-10-app")
#pz-fig("fig_11_room_schema.png", [Схема Room], "fig-11-app")
#pz-fig("fig_12_tasks_screen.jpg", [Экран задач], "fig-12-app")
#pz-fig("fig_13_tasks_notification.jpg", [Уведомление о задачах], "fig-13-app")
#appendix-heading("обязательное", level: 1)[Приложение Г. Диаграммы архитектуры и процессов]
#pz-fig("fig_01_start_sync.png", [Старт и синхронизация], "fig-01-app")
#pz-fig("fig_02_vault_lifecycle.png", [Жизненный цикл vault], "fig-02-app")
#pz-fig("fig_03_navigation_hub.png", [Навигация и SyncWorker], "fig-03-app")
#pz-fig("fig_04_domain_class.png", [Классы domain], "fig-04-app")
#pz-fig("fig_14_context_system.png", [Контекстная диаграмма], "fig-14-app")
#pz-fig("fig_15_bpmn_vault.png", [BPMN vault], "fig-15-app")
#pz-fig("fig_16_dfd_level0.png", [DFD уровень 0], "fig-16-app")
#pz-fig("fig_17_use_case.png", [Прецеденты], "fig-17-app")
#pz-fig("fig_18_deployment.png", [Развёртывание], "fig-18-app")
#pz-fig("fig_19_clean_architecture.png", [Clean Architecture], "fig-19-app")
#pz-fig("fig_20_oauth_sequence.png", [OAuth sequence], "fig-20-app")
#pz-fig("fig_21_encrypt_flow.png", [Поток шифрования], "fig-21-app")
#pz-fig("fig_22_cjm_vault.png", [CJM], "fig-22-app")
#pz-fig("fig_23_module_deps.png", [Модули Gradle], "fig-23-app")

View File

@@ -0,0 +1,266 @@
#import "@preview/modern-g7-32:0.2.0": abstract, appendix-heading, appendixes, enum-numbering, gost
// Нумерация с использованием кириллицы
#set enum(numbering: enum-numbering)
#show: gost.with(
ministry: "Наименование министерства (ведомства) или другого структурного образования, в систему которого входит организация-исполнитель",
organization: (
full: "Полное наименование организации — исполнителя НИР",
short: "Сокращённое наименование организации",
),
udk: "индекс УДК",
research-number: "регистрационный номер НИР",
report-number: "регистрационный номер отчета",
approved-by: (
name: "Фамилия И.О.",
position: "Должность, наимен. орг.",
year: 2017,
), // Гриф согласования
agreed-by: (
name: "Фамилия И.О.",
position: "Должность, наимен. орг.",
year: auto,
), // Гриф утверждения, год подставляется из аргумента year
report-type: "отчёт",
about: "О научно-исследовательской работе",
research: "Наименование НИР",
bare-subject: false, // Можно убрать "по теме"
subject: "Наименование отчёта",
manager: (
name: "Фамилия И.О.",
position: "Должность",
title: "Руководитель НИР,",
), // Руководитель отчёта
year: 2022,
stage: (type: "вид отчёта", num: 1), // Этап отчёта
federal: "Наименование федеральной программы",
part: 2, // Номер книги отчёта
city: "Город",
text-size: (default: 14pt, small: 10pt), // Можно указать размеры текста
indent: 1.25cm, // Можно указать отступ
hide-title: false, // Убрать ли титульный лист
title-footer-align: center, // Выравнивание города и года на титульном листе
pagination-align: center, // Выравнивание номера страницы
margin: (
left: 30mm,
right: 15mm,
top: 20mm,
bottom: 20mm,
), // Отступы страницы
add-pagebreaks: true, // Убрать ли разрывы страниц
performers: (
"Всероссийский институт научной и технической информации " + "Российской академии наук (ВИНИТИ РАН)",
// Можно указать организацию, к которой относятся следующие исполнители
(
name: "И.О. Фамилия",
position: "Должность",
part: "введение, раздел 1",
), // Можно добавить выполненную часть
(name: "И.О. Фамилия", position: "Должность"),
"Другая организация",
(name: "И.О. Фамилия", position: "Должность"),
(
name: "И.О. Фамилия",
position: "Должность",
co-performer: true,
), // Поддерживаются соисполнители
), // Если исполнитель один - он будет перенесён на титульный лист
)
#abstract(
"шаблон",
"typst",
"государственные стандарты",
"оформление документов",
"система вёрстки",
"автоматизация",
)[
Настоящий документ представляет собой описание шаблона modern-g7-32, разработанного для системы вёрстки #link(
"https://typst.app/",
)[typst] с целью автоматизации создания документов, соответствующих государственным стандартам. Шаблон упрощает
процесс оформления, в частности, титульного листа, благодаря функции `gost.with`, которая позволяет гибко настраивать
отображение информации и автоматически подставлять текущий год.
В документе рассмотрены основные элементы шаблона, включая оформление таблиц, блоков кода и изображений, а также
детально описана работа с аргументами функции `gost.with` для кастомизации титульной страницы. Представленный шаблон
предназначен для пользователей системы typst, стремящихся к созданию стандартизированных документов с минимальными
усилиями.
]
#outline()
= Введение
Шаблон modern-g7-32 предназначен для создания документов в строгом соответствии с ГОСТ. Он упрощает оформление
титульного листа, автоматизирует подстановку даты и позволяет легко управлять отображением информации.
= Документация шаблона Typst
Этот документ описывает особенности использования шаблона modern-g7-32 для системы вёрстки #link(
"https://typst.app/",
)[typst]. Ниже приведены примеры оформления элементов, таких как таблицы, блоки кода и изображения, а также подробное
описание работы с аргументами шаблона через функцию `gost.with` @examplewebsite.
= Основные элементы шаблона
Шаблон modern-g7-32 позволяет создавать документы, оформленные в строгом соответствии с государственными стандартами.
== Таблицы
Для создания таблиц используется функция `table()`, обёрнутая в макрос `#figure` для добавления подписи. Пример показан
на таблице @example-table.
#figure(
table(
columns: 4,
table.header([Заголовок 1], [Заголовок 2], [Т], [Заголовок 4]),
[Проверка], [Проверка], [Проверка], [Проверка],
[Проверка], [Проверка], [Проверка], [Проверка],
),
caption: [Пример таблицы с данными],
) <example-table>
== Блоки кода
Чтобы оформить блоки кода в документе, можно использовать синтаксис, похожий на Markdown. Пример указан на листинге
@example-code:
#figure(
```typst
// Пример кода на Typst:
#import "package.typ"
// Функция вывода сообщения
print("Hello, world!")
```,
caption: [Пример кода на Typst],
) <example-code>
Этот блок кода демонстрирует базовую структуру программы на Typst.
#pagebreak()
== Формулы
Чтобы оформить формулы в документе, можно использовать синтаксис typst-math. Примеры демонстрируют использование формул
в шаблоне:
Формула @example-formula-first показывает как применять сложные функции, формула @example-formula-second демонстрирует
как можно оформить матрицы, а формула @example-formula-third использование типов и обозначений @ivanov2020osnovy.
$ sum_(k=0)^n k = 1 + ... + n = (n(n+1)) / 2 $
<example-formula-first>
$ mat(1, 2, 3; 4, 5, 6; 7, 8, 9) * x = pi * psi(aleph/x) $
<example-formula-second>
$ cal(A) := { x in RR | x "натуральное" } $
<example-formula-third>
== Изображения
Изображения добавляются с помощью функции `image()` внутри макроса `#figure`. Пример вставки изображения указан на
рисунке @example-image.
#figure(
image("images/home.jpg", width: 40%),
caption: "Пример изображения",
) <example-image>
= Работа с аргументами шаблона в gost.with
Функция `gost.with` принимает множество аргументов, позволяющих настраивать титульную страницу и другие элементы
документа.
Рассмотрим основные возможности:
- Обязательные и необязательные аргументы. Многие параметры могут быть опущены, если вы не хотите, чтобы соответствующая
информация отображалась на титульном листе.
- Параметр auto (автоматический год). В аргументах, таких как `approved-by.year` или `year`, можно использовать значение
`auto`.
- Примеры аргументов: В заголовке шаблона задаются такие параметры, как:
+ `ministry-name` название министерства.
+ `organization` информация об учреждении (полное и сокращённое наименования).
+ `performers` список исполнителей с указанием их позиций.
== Работа с аргументами шаблона <with-numbering>
Как уже отмечалось, функция `gost.with` позволяет детально настраивать титульную страницу. Рассмотрим ключевые моменты:
- При передаче параметра `year: auto` текущий год будет вычисляться автоматически.
- Если не требуется выводить определённый элемент (например, номер инвентаризации), соответствующий аргумент можно не
указывать.
- Все аргументы передаются в виде именованных параметров, что обеспечивает гибкость конфигурации.
= Заключение <conclusion>
Шаблон modern-g7-32 для Typst значительно упрощает подготовку документов, соответствующих государственным стандартам. Он
обеспечивает удобное создание таблиц, блоков кода и изображений, а гибкая настройка через функцию `gost.with` позволяет
автоматически подставлять дату и исключать ненужные элементы с титульной страницы. Используйте данный шаблон для
создания качественных и стандартизированных документов @petrov2021analiz.
#bibliography("references.bib")
#show: appendixes
= Изображения в приложениях
Пример вставки изображения в приложение указан на рисунке @appendix-image.
#figure(
image("images/nature.jpg", width: 40%),
caption: "Пример изображения",
) <appendix-image>
== Изображения в под-приложениях
Пример вставки изображения в под-приложение указан на рисунке @appendix-image-3. #figure(
image("images/abstract.jpg", width: 40%),
caption: "Пример изображения",
) <appendix-image-3>
= Блоки кода
Чтобы оформить блоки кода в документе, можно использовать синтаксис, похожий на Markdown. Пример указан на листинге
@appendix-code:
#figure(
```typst
// Пример кода на Typst:
#import "package.typ"
// Функция вывода сообщения
print("Hello, world!")
```,
caption: [Пример кода на Typst],
) <appendix-code>
= Формулы
Чтобы оформить формулы в документе, можно использовать синтаксис typst-math. Примеры демонстрируют использование формул
в шаблоне:
$ sum_(k=0)^n k = 1 + ... + n = (n(n+1)) / 2 $ <appendix-formula>
как оформлять таблицы сказано в приложении @appendix-tables, а также в разделе с нумерацией @with-numbering.
== Таблицы <appendix-tables>
Для создания таблиц используется функция `table()`, обёрнутая в макрос `#figure` для добавления подписи. Пример показан
на таблице @appendix-table.
#figure(
table(
columns: 4,
table.header([Заголовок 1], [Заголовок 2], [Заголовок 3], [Заголовок 4]),
[Проверка], [Проверка], [Проверка], [Проверка],
[Проверка], [Проверка], [Проверка], [Проверка],
),
caption: [Пример таблицы с данными],
) <appendix-table>
#appendix-heading("справочное", level: 1)[Приложение с указанием статуса]
#lorem(100)
#appendix-heading("справочное", level: 2)[Приложение второго уровня со статусом]
#lorem(100)