Черновик ПЗ

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,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 и в настоящем введении.