Сделал что бы запускалось. Поправил мелкие ошибки
This commit is contained in:
AidarKC 2025-12-31 21:10:05 +03:00
parent 62ea49d1fc
commit f17d077f25
56 changed files with 424 additions and 873 deletions

95
DOC/doc-BD.md Normal file
View File

@ -0,0 +1,95 @@
Документ: shine-server-bd — таблицы и структура
Таблицы (что хранят)
solana_users — локальная таблица пользователей (логин + ключ устройства + опциональный solanaKey).
active_sessions — активные сессии пользователей (секреты сессии, время, push-подписки, IP/клиент-инфо).
users_params — сохранённые параметры пользователя (например, состояние просмотренности лент и прочие настройки), с подписью.
ip_geo_cache — кэш геолокации по IP (чтобы не дергать внешние сервисы каждый раз).
blockchain_state — агрегатное “текущее состояние” блокчейна по blockchainName (лимит, размер, последние хэши/номера).
blocks — сохранённые блоки/сообщения блокчейна (байты блока + связи “кому/куда” для адресации).
solana_users — пользователи и их ключи
login TEXT — логин пользователя (PRIMARY KEY)
deviceKey TEXT — публичный ключ устройства (обязателен)
solanaKey TEXT — дополнительный ключ Solana (может быть NULL)
active_sessions — активные сессии пользователя
sessionId TEXT — идентификатор сессии (PRIMARY KEY)
login TEXT — логин владельца сессии (FK → solana_users.login)
sessionPwd TEXT — пароль/секрет сессии (используется сервером)
storagePwd TEXT — пароль/секрет для шифрования хранилища/данных
sessionCreatedAtMs INTEGER — время создания сессии (ms)
lastAuthirificatedAtMs INTEGER — время последней успешной аутентификации/refresh (ms)
pushEndpoint TEXT — endpoint для web-push (nullable)
pushP256dhKey TEXT — ключ p256dh для web-push (nullable)
pushAuthKey TEXT — auth key для web-push (nullable)
clientIp TEXT — IP клиента при auth/refresh (nullable)
clientInfoFromClient TEXT — строка client-info от клиента (nullable)
clientInfoFromRequest TEXT — строка client-info, собранная сервером (nullable)
userLanguage TEXT — предпочитаемый язык пользователя (nullable)
users_params — параметры/состояния пользователя
login TEXT — логин владельца параметра (FK → solana_users.login)
param TEXT — имя параметра (в паре с login образует UNIQUE)
bch_channel_id INTEGER — id канала/ленты/контекста блокчейна (по умолчанию 0)
value TEXT — значение параметра (nullable)
time_ms INTEGER — время обновления параметра (ms)
pubkey_num INTEGER — номер/индекс публичного ключа, которым подписано значение
signature TEXT — подпись значения параметра (строка)
ip_geo_cache — кэш геоданных по IP
ip TEXT — IP-адрес (PRIMARY KEY)
geo TEXT — строка гео-описания (“Country, City” и т.п.) (nullable)
updated_at_ms INTEGER — время последнего обновления (ms)
blockchain_state — текущее состояние блокчейна по blockchainName
blockchainName TEXT — имя/идентификатор блокчейна (PRIMARY KEY)
login TEXT — владелец блокчейна (FK → solana_users.login)
blockchainKey TEXT — публичный ключ блокчейна (используется для подписи блоков)
size_limit INTEGER — лимит размера блокчейн-файла (байты)
file_size_bytes INTEGER — текущий размер файла блокчейна (байты)
last_global_number INTEGER — номер последнего глобального блока (genesis обычно -1)
last_global_hash TEXT — хэш последнего глобального блока (пусто для “нулевого” состояния)
updated_at_ms INTEGER — время обновления состояния (ms)
line0_last_number INTEGER — последний номер в линии 0
line0_last_hash TEXT — последний хэш в линии 0
line1_last_number INTEGER — последний номер в линии 1
line1_last_hash TEXT — последний хэш в линии 1
line2_last_number INTEGER — последний номер в линии 2
line2_last_hash TEXT — последний хэш в линии 2
line3_last_number INTEGER — последний номер в линии 3
line3_last_hash TEXT — последний хэш в линии 3
line4_last_number INTEGER — последний номер в линии 4
line4_last_hash TEXT — последний хэш в линии 4
line5_last_number INTEGER — последний номер в линии 5
line5_last_hash TEXT — последний хэш в линии 5
line6_last_number INTEGER — последний номер в линии 6
line6_last_hash TEXT — последний хэш в линии 6
line7_last_number INTEGER — последний номер в линии 7
line7_last_hash TEXT — последний хэш в линии 7
blocks — блоки/сообщения блокчейна (байтовое тело + адресация)
login TEXT — владелец (FK → solana_users.login)
bchName TEXT — имя блокчейна (FK → blockchain_state.blockchainName)
blockGlobalNumber INTEGER — глобальный номер блока
blockGlobalPreHashe TEXT — предыдущий глобальный хэш (строка)
blockLineIndex INTEGER — индекс линии (0..7)
blockLineNumber INTEGER — номер блока внутри линии
blockLinePreHashe TEXT — предыдущий хэш внутри линии
msgType INTEGER — тип сообщения/записи (код)
blockByte BLOB — сырые байты блока (nullable)
to_login TEXT — адресат логин (nullable)
toBchName TEXT — адресат блокчейн (nullable)
toBlockGlobalNumber INTEGER — целевой глобальный номер (nullable)
toBlockHashe TEXT — целевой хэш (nullable)

23
DOC/doc_all_libs.md Normal file
View File

@ -0,0 +1,23 @@
Перечень библиотек и их краткое описание
shine-server-log
Статический “сиренный” метод для максимально заметного критического лога администратору
shine-server-config
Минимальный конфиг-лоадер, который один раз читает application.properties и даёт доступ к параметрам.
Внешние настройки (2): server.1port=7070, db.path=data/shine.sqlite.
shine-server-geo
Утилиты, которые вытаскивают IP/язык/UA из Jetty WebSocket и (опционально) резолвят гео по IP с кэшем в БД.
shine-server-crypto
Базовые крипто-утилиты для SHA-256 и Ed25519 (BouncyCastle) + проверка подписи/хэша для .bch сущностей и маленький self-test.
shine-server-bd
Библиотека реалезующая всю работу с БД:
shine-server-blockchain
Библиотека, которая задаёт единый бинарный формат блоков (RAW+signature+hash), парсит/валидирует “тело” блока по type/version, и проверяет целостность/подпись цепочки через SHA-256 + Ed25519 с привязкой к login и предыдущим хэшам.
shine-server-protocol
Библиотека JSON-протокол поверх WebSocket для взаимодействия с клиентами.

View File

@ -0,0 +1,17 @@
shine-server-bd — это библиотека реалезующая всю работу с БД:
хранит пользователей/сессии/параметры/кэш IP→гео и данные блокчейна (состояние + блоки), предоставляя единый SqliteDbController для соединений, набор DAO под каждую таблицу (Singleton, методы с Connection для транзакций и без Connection — сами открывают/закрывают), и простые entity-модели как контейнеры данных для маппинга ResultSet↔Java.
Логика структуры классов (в двух словах):
shine.db.SqliteDbController — один вход в БД: читает db.path, при отсутствии файла создаёт БД, выдаёт новые Connection и настраивает PRAGMA.
shine.db.DatabaseInitializer — разовая сборка схемы (таблицы + индексы).
shine.db.entities.* — POJO-модели строк таблиц (без логики, только поля/геттеры/сеттеры + иногда удобные методы вроде getDeviceKeyByte()).
shine.db.dao.* — DAO по таблицам: ActiveSessionsDAO, SolanaUsersDAO, UserParamsDAO, IpGeoCacheDAO, BlockchainStateDAO, BlocksDAO; плюс “сервисные” DAO:
UserCreateDAO — атомарная регистрация пользователя в транзакции (BEGIN IMMEDIATE + rollback/commit).
// Временное решение позволяющее регистрировать новых пользователей
// атомарно и добавляет запись и в solana_users и в BlockchainState

View File

@ -0,0 +1,85 @@
shine-server-blockchain — это библиотека, которая задаёт формат блока, правила парсинга/валидации тела, крипто-проверку (hash+Ed25519) и безопасную работу с файлами блокчейна (data/<name>.bch через временный .tmp_bch).
Как устроена структура и логика работы
1) “Блок” как центральный объект (ядро)
BchBlockEntry — единая модель блока “как лежит на диске/в сети”:
читает/собирает байты в формате RAW + signature64 + hash32
сразу парсит body через BodyRecordParser
сразу проверяет что lineIndex совпадает с тем, что ожидает конкретный тип body (expectedLineIndex())
То есть: всё, что считается “блоком”, обязано быть самодостаточно валидным уже на этапе создания объекта.
2) “Body” как плагины по типам (расширяемая часть) ! <-- Новые типы записей добавлть сюда !
BodyRecord — интерфейс контракта для всех тел:
type/version — идентификаторы формата
expectedLineIndex() — жёсткое правило “в какой линии может жить”
check() — логическая валидация содержимого
toBytes() — сериализация обратно в бинарь
BodyRecordParser — диспетчер: читает первые 4 байта (type+ver) и выбирает нужный класс:
HeaderBody (lineIndex=0)
TextBody (lineIndex=1)
ReactionBody (lineIndex=2)
Добавление нового типа = добавить новый класс XxxBody + кейс в BodyRecordParser.
3) Криптография как отдельный слой проверки
BchCryptoVerifier отвечает за “как получить хэш и как проверить подпись”:
строит preimage = "SHiNE" + login + prevGlobalHash32 + prevLineHash32 + rawBytes
считает sha256(preimage) и сравнивает с hash32 внутри блока
проверяет Ed25519 подпись над hash32
Важно: BchBlockEntry не проверяет подпись — он проверяет структуру блока и правильность body/lineIndex, а криптопроверка вынесена отдельно.
4) Утилиты вокруг имени и файлов
BlockchainNameUtil — извлекает login из blockchainName (отрезает 3 символа суффикса).
FileStoreUtil — безопасное файловое хранилище:
5) Объяснение структуры работы
Типичный сценарий: пришёл блок → проверить → принять
Шаг 0. Контекст (что у нас уже есть снаружи)
Снаружи библиотеки (в сервере) у тебя уже известны:
userLogin — владелец блокчейна
publicKey32 — публичный ключ пользователя
prevGlobalHash32 — хэш предыдущего блока по глобальной цепи
prevLineHash32 — хэш предыдущего блока по текущей линии
Библиотека не хранит это сама, она ожидает, что сервер это передаст.
Шаг 1. Парсинг блока (структура + логика)
BchBlockEntry block = new BchBlockEntry(fullBytes);
Что происходит здесь автоматически:
проверяется длина блока
проверяется recordSize
парсится RAW-заголовок
парсится body через BodyRecordParser
проверяется, что lineIndex соответствует типу body
(HEADER → line 0, TEXT → line 1, REACTION → line 2 и т.д.)
На этом шаге никакой криптографии ещё нет — только структура и логика формата.
Если тут не упало исключение → блок структурно корректен.
Шаг 2. Подготовка данных для криптопроверки (они получаются просто из частей байтов полного блокас подписью)
byte[] rawBytes = block.getRawBytes();
byte[] signature64 = block.getSignature64();
byte[] hash32FromTail = block.getHash32();
Важно:
rawBytes — это ровно те байты, которые участвовали в хэшировании
hash32FromTail — это то, что автор блока положил внутрь блока
Шаг 3. Криптографическая проверка (ключевой вызов)
boolean ok = BchCryptoVerifier.verifyAll(
userLogin,
prevGlobalHash32,
prevLineHash32,
rawBytes,
signature64,
publicKey32,
hash32FromTail
);

View File

@ -0,0 +1,48 @@
Общая структура блока
Блок — это бинарная запись фиксированного формата:
[ RAW ][ signature64 ][ hash32 ]
RAW — данные блока (участвуют в хэшировании и подписи)
signature64 — Ed25519-подпись над hash32
hash32 — SHA-256 от preimage (привязан к цепочке и владельцу)
RAW-часть (BigEndian)
recordSize int32 — размер RAW (без signature+hash)
recordNumber int32 — глобальный номер блока
timestamp int64 — unix time (seconds)
lineIndex int16 — индекс линии
lineNumber int32 — номер блока внутри линии
bodyBytes bytes — тело блока (type+version+payload)
Общая структура блокчейна
Блокчейн — это:
линейная цепочка блоков (по recordNumber)
внутри неё — параллельные логические линии (lineIndex)
каждая линия имеет собственную нумерацию (lineNumber) и prevLineHash
вся цепочка связана ещё и prevGlobalHash
👉 Таким образом, каждый блок:
связан с предыдущим глобальным блоком
и с предыдущим блоком своей линии
Это даёт:
строгий порядок всей истории
и независимую валидацию логических потоков
Криптографический смысл блока
Хэш блока считается от:
"SHiNE" +
login +
prevGlobalHash32 +
prevLineHash32 +
RAW
Это означает:
блок жёстко привязан к владельцу (login)
блок невозможно перенести в другую цепочку
подмена предыдущего блока ломает всю цепь
Подпись Ed25519 делается над этим хэшем.

View File

@ -0,0 +1,36 @@
Формат и смысл существующих Body
1) HeaderBody (type=0, ver=1)
Линия: lineIndex = 0
Смысл:
Генезис-блок блокчейна, объявляет формат и владельца.
Содержит:
сигнатуру формата "SHiNE"
login владельца блокчейна
👉 Всегда первый блок, всегда в линии 0.
2) TextBody (type=1, ver=1)
Линия: lineIndex = 1
Смысл:
Основной контент — текстовые записи (посты, сообщения, дневник).
Содержит:
UTF-8 текст произвольной длины
👉 Это “основная история” блокчейна пользователя.
3) ReactionBody (type=2, ver=1)
Линия: lineIndex = 2
Смысл:
Связь с другим блокчейном или блоком (реакция, ответ, лайк, ссылка).
Содержит:
код реакции
имя целевого блокчейна
globalNumber целевого блока
hash32 целевого блока
👉 Это механизм межблокчейн-связей без изменения чужих цепочек.

View File

@ -0,0 +1,7 @@
shine-server-config
Минимальная библиотека конфигурации, предоставляющая потокобезопасный singleton-доступ к параметрам из application.properties.
Настройки:
server.port=7070 — порт запуска сервера
db.path=data/shine.sqlite — путь к SQLite базе данных

View File

@ -0,0 +1,8 @@
shine-server-crypto
О чём: базовые крипто-утилиты для SHA-256 и Ed25519 (BouncyCastle) + проверка подписи/хэша для .bch сущностей и маленький self-test.
Внешние методы, которые вызываются:
BchCryptoVerifier.verifyAll(), BchCryptoVerifier.buildPreimage(), Ed25519Util.generatePrivateKey(), Ed25519Util.generatePrivateKeyFromString(), Ed25519Util.derivePublicKey(), Ed25519Util.sign(), Ed25519Util.verify(), Ed25519Util.keyToBase64(), Ed25519Util.keyFromBase64(),
HashSHA256Util.sha256()
HashSHA256Util.loginToLoginId(), HashSHA256Util.loginIdFromLogin(),

View File

@ -0,0 +1,19 @@
shine-server-geo
Назначение: утилиты для получения “кто подключился” (IP/UA/язык) и геолокации по IP (с опциональным кэшем в БД).
Классы:
ClientInfoService — собирает строку UA/ch-ua/platform/mobile/remoteIP, вытаскивает реальный IP (приоритет: X-Forwarded-For → X-Real-IP → remoteAddress), парсит первый Accept-Language.
GeoLookupService — геолокация по IP через внешний API (ip-api.com), умеет вариант без кэша и с кэшем в таблице ip_geo_cache через IpGeoCacheDAO (пишет даже unknown), плюс метод получения внешнего IP через api.ipify.org.
GeoLookupTestMain — консольный тест: берёт IP из аргумента или определяет внешний, вызывает геолокацию и печатает результат + время.
Внешние (публично используемые) методы:
ClientInfoService.buildClientInfoString(Session) — формирует строку с User-Agent, client-hints и реальным IP клиента
ClientInfoService.extractClientIp(Session) — извлекает реальный IP (X-Forwarded-For / X-Real-IP / remoteAddress)
ClientInfoService.extractPreferredLanguageTag(Session) — возвращает основной язык клиента из Accept-Language
GeoLookupService.resolveCountryCityOrIp(String ip) — геолокация по IP без кэша (Country, City или unknown)
GeoLookupService.resolveCountryCityOrIpWithCache(String ip) — геолокация по IP с кэшированием в БД
GeoLookupService.fetchPublicIpOrDefault(String fallbackIp) — получение внешнего IP текущей машины

View File

@ -0,0 +1,10 @@
shine-log (BlockchainAdminNotifier)
Суть (1 предложение): единая точка для “красного” оповещения админа о критических проблемах консистентности, сейчас — через максимально заметный log.error, позже — через Telegram/email/webhook и т.п.
Структура (очень кратко):
BlockchainAdminNotifier (final utility)
BlockchainAdminNotifier.critical(String message)
BlockchainAdminNotifier.critical(String message, Throwable t)

View File

@ -1,205 +0,0 @@
формат добавления блоков и из чего реально состоит блок, плюс как именно считаются хэши и подписи.
1) Формат добавления блоков в файл .bch
Файл — это просто конкатенация блоков один за другим, без разделителей:
BLOCK_0 | BLOCK_1 | BLOCK_2 | ...
Чтобы читать файл, ты идёшь по нему последовательно:
Читаешь первые 4 байта = recordSize
Понимаешь, сколько всего байт занимает блок:
fullBlockLen = recordSize + 64 + 32
Считываешь оставшиеся байты блока и парсишь:
RAW часть длиной recordSize
затем signature64
затем hash32
Это удобно тем, что файл можно дописывать (APPEND) хоть по сети, хоть локально: блоки самодостаточные.
2) Из чего состоит блок (формат блока)
2.1. RAW (участвует в preimage и верификации; подпись/хэш к нему «пришиты»)
BigEndian, фиксированный заголовок + body:
RAW:
[4] recordSize (int) — размер RAW включая эти 4 байта, но без signature+hash
[4] recordNumber (int) — глобальный номер блока (цепочка)
[8] timestamp (long) — unix seconds
[2] line (short) — линия (у вас пока по сути одна)
[4] lineNumber (int) — номер в линии (у вас пока обычно равен global)
[N] bodyBytes — тело, начинается с [2] type + [2] version, дальше payload
RAW_HEADER_SIZE = 4 + 4 + 8 + 2 + 4 = 22 байта
2.2. TAIL (не входит в recordSize)
[64] signature64 — Ed25519 подпись
[32] hash32 — SHA-256 хэш
Итого полный блок:
FULL = RAW(recordSize bytes) + signature64 + hash32
3) Формат body (тела блока)
У тебя правило железное: каждый body начинается одинаково:
[2] type
[2] version
дальше payload по типу/версии
Парсер делает ключ:
key = (type<<16) | version
3.1 HeaderBody (type=0, ver=1)
Полный bodyBytes:
[2] type=0
[2] ver=1
[8] tag "SHiNE001" (ASCII)
[1] loginLength (uint8)
[N] login UTF-8
Это «генезис-смысл» для идентичности: в теле блока явно хранится логин.
3.2 TextBody (type=1, ver=1)
[2] type=1
[2] ver=1
[N] message UTF-8
4) Как считается хэш (hash32)
Важно: hash32 в блоке — это не hash(rawBytes).
Ты считаешь:
4.1 Preimage
preimage =
"SHiNE" +
[1] loginLen + loginBytes(UTF-8) +
prevGlobalHash32 +
prevLineHash32 +
rawBytes
Где:
"SHiNE" — доменная метка (защита от «подпиши этим же ключом что-то другое»)
loginLen + loginBytes — привязка блока к пользователю/цепочке
prevGlobalHash32 — сцепление по глобальной цепи
prevLineHash32 — сцепление по линии
rawBytes — содержимое текущего блока без подписи/хэша
4.2 Сам хэш
hash32 = SHA-256(preimage)
И именно этот hash32 должен лежать в конце блока.
5) Как считается подпись (signature64)
Подписывается не rawBytes, а hash32:
signature64 = Ed25519.sign(hash32, privateKey)
Проверка:
Пересобираем preimage
Считаем hash32 = sha256(preimage)
Сверяем hash32 с тем, что лежит в блоке
Проверяем подпись:
Ed25519.verify(hash32, signature64, publicKey32)
Это хорошая схема: подпись всегда фиксированного размера, а хэш — единая точка правды.
6) Что является «цепочкой» и чем блоки сцеплены
Сцепление идёт через prevGlobalHash32 и prevLineHash32, которые ты передаёшь в verifyAll(...).
То есть логика добавления нового блока (в терминах данных) такая:
берём последний блок
достаём его hash32
это и будет prevGlobalHash32 для нового блока
prevLineHash32 — по той линии, куда добавляем (у вас пока это то же самое, потому что одна линия/нет разветвления)
У тебя даже отдельно отмечено: пока нет отдельных линий, lastLineHash == lastGlobalHash — значит сейчас обе цепочки фактически совпадают.
7) Мини-схема “как добавить новый блок” (пошагово)
Собрать BodyRecord → получить bodyBytes = body.toBytes()
Собрать RAW:
recordNumber = lastRecordNumber + 1
timestamp = nowSeconds
line (скорее всего 0 или 1 — как вы договорились)
lineNumber = lastLineNumber + 1 (сейчас равно global)
recordSize = RAW_HEADER_SIZE + bodyBytes.length
rawBytes = new BchBlockEntry(...).getRawBytes() (или собрать вручную)
preimage = buildPreimage(login, prevGlobalHash32, prevLineHash32, rawBytes)
hash32 = sha256(preimage)
signature64 = Ed25519.sign(hash32, privateKey)
Собрать финальный BchBlockEntry(..., signature64, hash32)
Дописать toBytes() в файл .bch
8) Важные нюансы (чтобы не словить «тихий баг»)
recordSize включает себя (первые 4 байта) — это ок, просто помнить.
bodyLen = recordSize - RAW_HEADER_SIZE — значит RAW_HEADER_SIZE обязан быть всегда синхронен с реальным RAW.
login в preimage ограничен 255 байт (у тебя [1] loginLen) — это правильно и предсказуемо.
В HeaderBody tag фиксирован "SHiNE001" — фактически версия протокола body-headerа.

View File

@ -11,7 +11,7 @@ import java.util.Objects;
* Полный bodyBytes:
* [2] type=0
* [2] version=1
* [8] tag ASCII "SHiNE001"
* [8] tag ASCII "SHiNE"
* [1] loginLength=N (uint8)
* [N] login UTF-8
*
@ -23,9 +23,9 @@ public final class HeaderBody implements BodyRecord {
public static final short TYPE = 0;
public static final short VER = 1;
public static final String TAG = "SHiNE001";
public static final String TAG = "SHiNE";
public final String tag; // "SHiNE001"
public final String tag; // "SHiNE"
public final String login;
/** Десериализация из полного bodyBytes (включая type/version). */
@ -42,7 +42,7 @@ public final class HeaderBody implements BodyRecord {
if (bb.remaining() < 8 + 1)
throw new IllegalArgumentException("Header payload too short");
byte[] tagBytes = new byte[8];
byte[] tagBytes = new byte[5];
bb.get(tagBytes);
String t = new String(tagBytes, StandardCharsets.US_ASCII);
if (!TAG.equals(t)) throw new IllegalArgumentException("Bad tag: " + t);

View File

@ -1,27 +0,0 @@
# utils.files
Хранение блокчейнов в виде файлов в папке `data/`.
---
## FileStoreUtil
Singleton для чтения и записи `.bch` файлов.
- `newBlockchain(id, data)` — создать новый файл `data/<id>.bch`
- `addDataToBlockchain(id, data)` — добавить байты в конец файла
- `readAllDataFromBlockchain(id)` — прочитать весь файл как массив байт
Каждый файл содержит последовательность полных блоков `[RAW][signature64][hash32]...`
---
## FileStoreUtilSelfTest
Тест: создаёт, дописывает и читает файл, чтобы проверить корректность.
---
Пока используется как временное файловое хранилище.
В будущем всё это уйдёт в SQL.

View File

@ -1,14 +0,0 @@
# utils.search
Поиск пользователей по логину среди зарегистрированных блокчейнов.
---
## UserSearchService
Singleton-сервис для поиска первых 5 логинов, содержащих подстроку (без учёта регистра).
Основные методы:
- `searchFirst5(String query)` — вернуть список `Pair(id, login)`
- `packPair(Pair p)` — упаковать пару в бинарный ответ `[8]id + [1]len + [len]login`
Используется сервером в операции `SEARCH_USERS (op=30)` для ответа клиенту.

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
OUTFILE="all_files.txt"
# очищаем или создаём файл
: > "$OUTFILE"
# собрать только *.java файлы и вывести их содержимое в файл
find . -type f -name "*.java" | sort | while read -r f; do
cat "$f" >> "$OUTFILE"
echo >> "$OUTFILE" # пустая строка-разделитель
done
echo "Готово! Все .java файлы собраны в $OUTFILE"

View File

@ -18,42 +18,6 @@ public final class HashSHA256Util {
return out;
}
/** Получить loginId из строки логина.
* Алгоритм:
* - login -> UTF-8 bytes
* - SHA-256
* - берём последние 8 байт (справа)
* - интерпретируем как signed long (BigEndian)
*/
public static long loginToLoginId(String login) {
if (login == null || login.isBlank())
throw new IllegalArgumentException("login is null or empty");
byte[] hash = sha256(login.getBytes(StandardCharsets.UTF_8));
// последние 8 байт SHA-256
return ByteBuffer.wrap(hash, 24, 8)
.order(ByteOrder.BIG_ENDIAN)
.getLong();
}
/**
* loginId = last 8 bytes of sha256(login UTF-8), big-endian.
* (берём 8 байт справа и читаем как unsigned long в BE)
*/
public static long loginIdFromLogin(String login) {
if (login == null || login.isBlank())
throw new IllegalArgumentException("login is blank");
byte[] h = sha256(login.getBytes(StandardCharsets.UTF_8));
long v = 0;
for (int i = 24; i < 32; i++) {
v = (v << 8) | (h[i] & 0xFFL);
}
return v;
}
/** Инкрементальный SHA-256 (если нужно будет кормить по кускам). */
public static final class Sha256 {
private final SHA256Digest d = new SHA256Digest();

View File

@ -1,53 +0,0 @@
package shine.db.dao;
import shine.db.SqliteDbController;
import shine.db.entities.BlockchainStateEntry;
import java.sql.*;
/**
* SolanaBlockchainsDAO таблица блокчейнов пользователя.
*
* Сейчас физически это blockchain_state, потому что:
* - у одного login может быть несколько blockchainName
* - у каждого blockchainName свой blockchainKey и size_limit
*
* Правило:
* - методы с Connection НЕ закрывают соединение
* - методы без Connection сами открывают и закрывают соединение
*/
public final class SolanaBlockchainsDAO {
private static volatile SolanaBlockchainsDAO instance;
private final SqliteDbController db = SqliteDbController.getInstance();
private final BlockchainStateDAO stateDao = BlockchainStateDAO.getInstance();
private SolanaBlockchainsDAO() {}
public static SolanaBlockchainsDAO getInstance() {
if (instance == null) {
synchronized (SolanaBlockchainsDAO.class) {
if (instance == null) instance = new SolanaBlockchainsDAO();
}
}
return instance;
}
public BlockchainStateEntry getByBlockchainName(String blockchainName) throws SQLException {
return stateDao.getByBlockchainName(blockchainName);
}
public BlockchainStateEntry getByBlockchainName(Connection c, String blockchainName) throws SQLException {
return stateDao.getByBlockchainName(c, blockchainName);
}
/** Для HEADER: проверка, что blockchain_state существует и last_global_number=-1. */
public BlockchainStateEntry requireExistingAtGenesis(Connection c, String blockchainName) throws SQLException {
return stateDao.requireExistingAtGenesis(c, blockchainName);
}
/** Для добавления блока: атомарная проверка лимита + увеличение размера файла. */
public boolean tryIncreaseFileSizeWithinLimit(Connection c, String blockchainName, long deltaBytes, long nowMs) throws SQLException {
return stateDao.tryIncreaseFileSizeWithinLimit(c, blockchainName, deltaBytes, nowMs);
}
}

View File

@ -1,44 +0,0 @@
# Структура БД SHiNE (кратко)
## Таблица `solana_users`
Локальная копия данных о пользователях из Solana.
Поля:
- `login` (TEXT) — логин пользователя.
- `loginId` (INTEGER, PK) — ID пользователя, основной ключ.
- `bchId` (INTEGER) — ID блокчейна пользователя.
- `pubkey0` (TEXT) — первый публичный ключ.
- `pubkey1` (TEXT) — второй публичный ключ.
- `bchLimit` (INTEGER, NULL) — произвольный лимит для пользователя (опционально).
---
## Таблица `active_sessions`
Активные сессии пользователей (WebSocket/WSS + Web Push).
Поля:
- `sessionId` (INTEGER, PK) — ID сессии, генерируется приложением.
- `session_pwd` (TEXT) — секрет/пароль сессии.
- `loginId` (INTEGER, FK → solana_users.loginId) — владелец сессии.
- `time_ms` (INTEGER) — время создания/активности сессии (мс от эпохи).
- `pubkey_num` (INTEGER) — номер ключа пользователя, которым подписывались данные.
- `push_endpoint` (TEXT, NULL) — endpoint Web Push.
- `push_p256dh_key` (TEXT, NULL) — p256dh-ключ Web Push.
- `push_auth_key` (TEXT, NULL) — auth-ключ Web Push.
---
## Таблица `users_params`
Сохранённые параметры и состояния пользователя (например, до какого сообщения прочитана лента).
Поля:
- `loginId` (INTEGER, FK → solana_users.loginId) — пользователь.
- `param` (TEXT) — имя параметра (ключ).
- `bch_channel_id` (INTEGER, DEFAULT 0) — ID канала/ленты который просмотрен.
- `value` (TEXT, NULL) — значение параметра (строка).
- `time_ms` (INTEGER) — время последнего обновления (мс от эпохи).
- `pubkey_num` (INTEGER) — номер ключа, которым подписано значение.
- `signature` (TEXT, NULL) — подпись значения.
Ограничения:
- `UNIQUE(loginId, param)`у одного пользователя каждый `param` только один раз.

View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
set -euo pipefail
OUTFILE="all_files.txt"
# очищаем или создаём файл
: > "$OUTFILE"
# собрать только *.java файлы и вывести их содержимое в файл
find . -type f -name "*.java" | sort | while read -r f; do
cat "$f" >> "$OUTFILE"
echo >> "$OUTFILE" # пустая строка-разделитель
done
echo "Готово! Все .java файлы собраны в $OUTFILE"

View File

@ -1,13 +1,12 @@
package server.logic.ws_protocol.JSON;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_AuthChallenge_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_CreateAuthSession_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_RefreshSession_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_CloseActiveSession_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_ListSessions_Request;
import server.logic.ws_protocol.JSON.entyties.blockchain.Net_AddBlock_Request;
import server.logic.ws_protocol.JSON.entyties.tempToTest.Net_AddUser_Request;
import server.logic.ws_protocol.JSON.entyties.*;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.*;
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.*;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.*;
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Request;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.handlers.auth.Net_AuthChallenge_Handler;
import server.logic.ws_protocol.JSON.handlers.auth.Net_CreateAuthSession__Handler;

View File

@ -2,8 +2,7 @@ package server.logic.ws_protocol.JSON.handlers.auth;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_AuthChallenge_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_AuthChallenge_Response;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.*;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;

View File

@ -4,8 +4,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_CloseActiveSession_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_CloseActiveSession_Response;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;

View File

@ -5,10 +5,9 @@ import org.slf4j.LoggerFactory;
import org.eclipse.jetty.websocket.api.Session;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_CreateAuthSession_Response;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_CreateAuthSession_Request;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;

View File

@ -3,13 +3,12 @@ package server.logic.ws_protocol.JSON.handlers.auth;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_ListSessions_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_ListSessions_Response;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_ListSessions_Response.SessionInfo;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.Net_ListSessions_Response.SessionInfo;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.ActiveSessionsDAO;
import shine.db.entities.ActiveSessionEntry;
@ -144,7 +143,7 @@ public class Net_ListSessions_Handler implements JsonMessageHandler {
// 4) Собираем DTO с геолокацией
List<SessionInfo> resultList = new ArrayList<>();
for (ActiveSessionEntry s : sessions) {
SessionInfo info = new SessionInfo();
SessionInfo info = new Net_ListSessions_Response.SessionInfo();
info.setSessionId(s.getSessionId());
info.setClientInfoFromClient(s.getClientInfoFromClient());
info.setClientInfoFromRequest(s.getClientInfoFromRequest());

View File

@ -4,8 +4,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_RefreshSession_Request;
import server.logic.ws_protocol.JSON.entyties.Auth.Net_RefreshSession_Response;
import server.logic.ws_protocol.JSON.handlers.auth.entyties.*;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.Auth;
package server.logic.ws_protocol.JSON.handlers.auth.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;

View File

@ -7,8 +7,10 @@ import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.entyties.blockchain.Net_AddBlock_Request;
import server.logic.ws_protocol.JSON.entyties.blockchain.Net_AddBlock_Response;
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils.BlockchainLocks;
import server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils.BlockchainWriter;
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Request;
import server.logic.ws_protocol.JSON.handlers.blockchain.entyties.Net_AddBlock_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.WireCodes;
import shine.db.dao.BlockchainStateDAO;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.handlers.blockchain;
package server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.handlers.blockchain;
package server.logic.ws_protocol.JSON.handlers.blockchain.Net_AddBlock_Handler_utils;
import blockchain.BchBlockEntry;
import blockchain.body.ReactionBody;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.blockchain;
package server.logic.ws_protocol.JSON.handlers.blockchain.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.blockchain;
package server.logic.ws_protocol.JSON.handlers.blockchain.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;

View File

@ -5,8 +5,8 @@ import org.slf4j.LoggerFactory;
import server.logic.ws_protocol.JSON.ConnectionContext;
import server.logic.ws_protocol.JSON.entyties.Net_Request;
import server.logic.ws_protocol.JSON.entyties.Net_Response;
import server.logic.ws_protocol.JSON.entyties.tempToTest.Net_AddUser_Request;
import server.logic.ws_protocol.JSON.entyties.tempToTest.Net_AddUser_Response;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Request;
import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_AddUser_Response;
import server.logic.ws_protocol.JSON.handlers.JsonMessageHandler;
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
import server.logic.ws_protocol.WireCodes;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.tempToTest;
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Request;

View File

@ -1,4 +1,4 @@
package server.logic.ws_protocol.JSON.entyties.tempToTest;
package server.logic.ws_protocol.JSON.handlers.tempToTest.entyties;
import server.logic.ws_protocol.JSON.entyties.Net_Response;

View File

@ -16,7 +16,7 @@
*
* | Смещение | Размер | Поле | Формат | Описание |
* |-----------|--------|--------------------|---------|-----------|
* | 0x00 | 8 | tag | ASCII | Статическая сигнатура "SHiNE001" |
* | 0x00 | 8 | tag | ASCII | Статическая сигнатура "SHiNE" |
*
* | 0x10 | 1 | userLoginLength=N | uint8 | Длина логина пользователя |
* | 0x11 | N | userLogin | UTF-8 | Логин пользователя |
@ -34,7 +34,7 @@
* ----------------------------------------------------------------------------
* 💡 Пример структуры в байтах:
*
* 0000: 53 48 69 4E 45 30 30 31 "SHiNE001"
* 0000: 53 48 69 4E 45 30 30 31 "SHiNE"
* 0008: 00 00 00 00 01 23 45 67 blockchainId
* 0010: 05 userLoginLength = 5
* 0011: 41 69 64 61 72 userLogin = "Aidar"

View File

@ -18,7 +18,7 @@ public final class InboundMessageProcessor {
private static final Logger log = LoggerFactory.getLogger(InboundMessageProcessor.class);
private static final Map<Integer, MessageHandler> HANDLERS = Map.of(
WireCodes.Op.PING, new PingHandler()
// WireCodes.Op.PING, new PingHandler()
// WireCodes.Op.ADD_BLOCK, new AddBlockHandler(),
// WireCodes.Op.GET_BLOCKCHAIN,new GetBlockchainHandler()
// WireCodes.Op.SEARCH_USERS, new SearchUsersHandler(),

View File

@ -1,250 +0,0 @@
//package server.logic.ws_protocol.binary.handlers;
//
//import blockchain.BchBlockEntry;
//import blockchain.body.BodyRecord;
//import blockchain.BodyRecordParser;
//import blockchain.body.HeaderBody;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import server.logic.ws_protocol.WireCodes;
//import utils.blockchain.BchInfoEntry;
//import utils.blockchain.BchInfoManager;
//import utils.crypto.BchCryptoVerifier;
//import utils.files.FileStoreUtil;
//
//import java.nio.ByteBuffer;
//import java.nio.ByteOrder;
//import java.util.Arrays;
//
///**
// * AddBlockHandler обработчик команды "добавить блок" (ADD_BLOCK)
// * ---------------------------------------------------------------
// * Принимает бинарное сообщение от клиента и добавляет новый блок в цепочку.
// *.
// * Формат входного сообщения (msg):
// * [0..3] 4 байта: код операции (WireCodes.ADD_BLOCK)
// * [4..11] 8 байт: blockchainId (уникальный идентификатор цепочки)
// * [12..] байты полного блока .bch:
// * 4 байта recordSize = M + 18
// * 4 байта recordNumber
// * 8 байт timestamp
// * 2 байта recordType
// * 2 байта recordVersion
// * M байт body (содержимое блока)
// * 64 байта signature (Ed25519)
// * 32 байта hash (SHA-256)
// *.
// * ---------------------------------------------------------------
// * Алгоритм работы:
// *.
// * 1 Распаковать BchBlockEntry из msg (т.е. выделить тело блока и подписи).
// * 2 Найти описание цепочки (BchInfoEntry) по blockchainId.
// *.
// * Если описания нет (цепочка ещё не существует):
// * принимаем только блок типа 0 (HeaderBody) и номера 0;
// * парсим его, создаём новый BchInfoEntry на основе данных заголовка;
// * проверяем подпись и хэш;
// * проверяем корректность тела блока (check);
// * сохраняем блок и создаём новый blockchain-файл;
// * добавляем цепочку в менеджер BchInfoManager.
// * (💡 временное решение: создание цепочки допустимо только через HeaderBody)
// *.
// * Если цепочка уже существует:
// * проверяем, что номер блока равен (lastBlockNumber + 1);
// * проверяем подпись и хэш;
// * проверяем тело блока (check);
// * добавляем блок в файл цепочки;
// * обновляем состояние BchInfoEntry (номер, хэш, размер).
// *.
// * 3 Если все проверки пройдены возвращаем статус OK.
// *.
// * Таким образом, единственное различие между первым блоком и последующими
// * момент инициализации описания цепочки (BchInfoEntry).
// * Всё остальное (валидация, подпись, добавление, обновление) выполняется одинаково.
// */
//public class AddBlockHandler implements MessageHandler {
//
// private static final Logger log = LoggerFactory.getLogger(AddBlockHandler.class);
//
// @Override
// public byte[] handle(byte[] msg) {
// try {
// // =====================================================================
// // 1 Проверка минимальной длины пакета
// // =====================================================================
// int minFull = BchBlockEntry.RAW_HEADER_SIZE + BchBlockEntry.SIGNATURE_LEN + BchBlockEntry.HASH_LEN;
// // (RAW_HEADER_SIZE = 18 байт, подпись = 64, хэш = 32)
// if (msg.length < 4 + 8 + minFull)
// return code(WireCodes.Status.BAD_REQUEST);
//
// // =====================================================================
// // 2 Извлекаем blockchainId (8 байт начиная с позиции 4)
// // =====================================================================
// long blockchainId = ByteBuffer.wrap(msg, 4, 8)
// .order(ByteOrder.BIG_ENDIAN)
// .getLong();
//
// // Всё, что дальше, это бинарное содержимое блока .bch
// int offset = 12; // первые 12 байт = код + blockchainId
//
// // =====================================================================
// // 3 Парсим блок (RAW + подпись + хэш)
// // =====================================================================
// byte[] fullBlock = Arrays.copyOfRange(msg, offset, msg.length);
// BchBlockEntry block = new BchBlockEntry(fullBlock); // сам распакует RAW-часть и подписи
//
// // =====================================================================
// // 4 Получаем текущее описание цепочки (BchInfoEntry)
// // =====================================================================
// BchInfoManager info = BchInfoManager.getInstance();
// BchInfoEntry chain = info.getBchInfo(blockchainId);
//
// byte[] prevHash32;
// int expectedNum;
// String userLogin;
// byte[] publicKey32;
//
// // =====================================================================
// // 🧩 СЦЕНАРИЙ 1: цепочка отсутствует создаём новую
// // =====================================================================
// if (chain == null) {
// // Допускаем только блок-заголовок (type=0, num=0)
// if (block.recordType != BchBlockEntry.TYPE_HEADER || block.recordNumber != 0) {
// log.warn("Попытка создать новую цепочку без корректного заголовка (type={}, num={})",
// block.recordType, block.recordNumber);
// return code(WireCodes.Status.BAD_REQUEST);
// }
//
// // Парсим тело блока HeaderBody
// BodyRecord body = BodyRecordParser.parse(block.recordType, block.recordTypeVersion, block.body).check();
// if (!(body instanceof HeaderBody))
// return code(WireCodes.Status.BAD_REQUEST);
//
// HeaderBody hb = (HeaderBody) body;
//
// // Проверяем, что blockchainId совпадает
// if (hb.blockchainId != blockchainId) {
// log.warn("Несовпадение blockchainId в заголовке (ожидалось {}, получено {})",
// blockchainId, hb.blockchainId);
// return code(WireCodes.Status.BAD_REQUEST);
// }
//
// // Проверяем подпись и хэш первого блока (предыдущий хэш = 0)
// prevHash32 = new byte[32];
// boolean verified = BchCryptoVerifier.verifyAll(
// hb.userLogin,
// blockchainId,
// prevHash32,
// block.rawBytes,
// block.getSignature64(),
// block.getHash32(),
// hb.publicKey32
// );
// if (!verified) {
// log.warn("❌ Подпись не прошла проверку при создании цепочки blockchainId={}", blockchainId);
// return code(WireCodes.Status.UNVERIFIED);
// }
//
// // Всё хорошо: создаём новую цепочку
// info.addBlockchain(blockchainId, hb.userLogin, hb.publicKey32, Integer.MAX_VALUE);
// info.updateBlockchainState(blockchainId, block.recordNumber, bytesToHex(block.getHash32()), fullBlock.length);
//
// FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, fullBlock);
//
// log.info("✅ Создана новая цепочка blockchainId={}, user={}, blockNum={}",
// blockchainId, hb.userLogin, block.recordNumber);
//
// return code(WireCodes.Status.OK);
// }
//
// // =====================================================================
// // 🧩 СЦЕНАРИЙ 2: цепочка существует добавляем новый блок
// // =====================================================================
// expectedNum = chain.lastBlockNumber + 1;
//
// // Проверка последовательности (и отправка lastBlockNumber)
// if (block.recordNumber < expectedNum) {
// log.info("🔁 Блок {} уже существует, последний = {}", block.recordNumber, chain.lastBlockNumber);
// ByteBuffer out = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
// out.putInt(WireCodes.Status.BLOCK_ALREADY_EXISTS);
// out.putInt(chain.lastBlockNumber);
// return out.array();
// }
// if (block.recordNumber > expectedNum) {
// log.warn("⚠️ Нарушена последовательность: получен {}, ожидался {}", block.recordNumber, expectedNum);
// ByteBuffer out = ByteBuffer.allocate(8).order(ByteOrder.BIG_ENDIAN);
// out.putInt(WireCodes.Status.OUT_OF_SEQUENCE);
// out.putInt(chain.lastBlockNumber);
// return out.array();
// }
//
// userLogin = chain.userLogin;
// publicKey32 = chain.getPublicKey32();
//
// // Хэш предыдущего блока (или 32 нуля, если это первый)
// prevHash32 = (chain.lastBlockHash == null || chain.lastBlockHash.isEmpty())
// ? new byte[32]
// : hexToBytes(chain.lastBlockHash);
//
// // Проверяем подпись и хэш
// boolean verified = BchCryptoVerifier.verifyAll(
// userLogin,
// blockchainId,
// prevHash32,
// block.rawBytes,
// block.getSignature64(),
// block.getHash32(),
// publicKey32
// );
// if (!verified) {
// log.warn("❌ Подпись не прошла проверку: chainId={}, blockNum={}", blockchainId, block.recordNumber);
// return code(WireCodes.Status.UNVERIFIED);
// }
//
// // Проверяем тело блока (например, корректный UTF-8 или структура)
// BodyRecord body = BodyRecordParser.parse(block.recordType, block.recordTypeVersion, block.body).check();
//
// // Добавляем блок в файл цепочки
// FileStoreUtil.getInstance().addDataToBlockchain(blockchainId, fullBlock);
//
// // Обновляем состояние цепочки (номер, хэш, размер)
// int newSize = chain.blockchainSize + fullBlock.length;
// info.updateBlockchainState(blockchainId, block.recordNumber, bytesToHex(block.getHash32()), newSize);
//
// log.info("✅ Блок добавлен: chain={}, num={}, type={}, bytes={}",
// blockchainId, block.recordNumber, block.recordType, fullBlock.length);
//
// return code(WireCodes.Status.OK);
//
// } catch (Exception e) {
// log.error("❌ ADD_BLOCK: внутренняя ошибка при обработке", e);
// return code(WireCodes.Status.INTERNAL_ERROR);
// }
// }
//
// // =====================================================================
// // Утилиты
// // =====================================================================
//
// /** Преобразовать статус (int) в 4 байта BigEndian. */
// private static byte[] code(int status) {
// return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(status).array();
// }
//
// /** Конвертация HEX → bytes (для хэшей). */
// private static byte[] hexToBytes(String hex) {
// int len = hex.length();
// byte[] out = new byte[len / 2];
// for (int i = 0; i < len; i += 2)
// out[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
// return out;
// }
//
// /** Конвертация bytes → HEX (для сохранения в BchInfo). */
// private static String bytesToHex(byte[] b) {
// StringBuilder sb = new StringBuilder(b.length * 2);
// for (byte x : b) sb.append(String.format("%02x", x));
// return sb.toString();
// }
//}
//

View File

@ -1,53 +0,0 @@
//package server.logic.ws_protocol.binary.handlers;
//
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import server.logic.ws_protocol.WireCodes;
//import utils.files.FileStoreUtil;
//
//import java.nio.ByteBuffer;
//import java.nio.ByteOrder;
//
///**
// * Возврат полного содержимого блокчейна (GET_BLOCKCHAIN).
// */
//public class GetBlockchainHandler implements MessageHandler {
// private static final Logger log = LoggerFactory.getLogger(GetBlockchainHandler.class);
//
// @Override
// public byte[] handle(byte[] msg) {
// try {
// if (msg.length < 12)
// return intTo4Bytes(WireCodes.Status.BAD_REQUEST);
//
// long id = ByteBuffer.wrap(msg, 4, 8)
// .order(ByteOrder.BIG_ENDIAN)
// .getLong();
//
// FileStoreUtil fs = FileStoreUtil.getInstance();
// byte[] data = fs.readAllDataFromBlockchain(id);
//
// return packOk(data);
//
// } catch (IllegalStateException e) {
// log.warn("GET_BLOCKCHAIN: файл не найден ({})", e.getMessage());
// return intTo4Bytes(WireCodes.Status.CHAIN_NOT_FOUND);
// } catch (Exception e) {
// log.error("GET_BLOCKCHAIN: ошибка", e);
// return intTo4Bytes(WireCodes.Status.INTERNAL_ERROR);
// }
// }
//
// private static byte[] packOk(byte[] data) {
// if (data == null) data = new byte[0];
// ByteBuffer out = ByteBuffer.allocate(8 + data.length).order(ByteOrder.BIG_ENDIAN);
// out.putInt(WireCodes.Status.OK);
// out.putInt(data.length);
// out.put(data);
// return out.array();
// }
//
// private static byte[] intTo4Bytes(int code) {
// return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(code).array();
// }
//}

View File

@ -1,66 +0,0 @@
//package server.logic.ws_protocol.binary.handlers;
//
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import server.logic.ws_protocol.WireCodes;
//import utils.blockchain.BchInfoEntry;
//import utils.blockchain.BchInfoManager;
//
//import java.nio.ByteBuffer;
//import java.nio.ByteOrder;
//import java.util.Arrays;
//
///**
// * Возврат информации о последнем блоке цепочки (GET_LAST_BLOCK_INFO).
// */
//public class GetLastBlockInfoHandler implements MessageHandler {
// private static final Logger log = LoggerFactory.getLogger(GetLastBlockInfoHandler.class);
//
// @Override
// public byte[] handle(byte[] msg) {
// try {
// if (msg.length < 12)
// return intTo4Bytes(WireCodes.Status.BAD_REQUEST);
//
// long blockchainId = ByteBuffer.wrap(msg, 4, 8)
// .order(ByteOrder.BIG_ENDIAN)
// .getLong();
//
// BchInfoManager mgr = BchInfoManager.getInstance();
// BchInfoEntry entry = mgr.getBchInfo(blockchainId);
// if (entry == null)
// return intTo4Bytes(WireCodes.Status.CHAIN_NOT_FOUND);
//
// int lastNum = entry.lastBlockNumber;
// byte[] hash = hexToBytes(entry.lastBlockHash);
//
// ByteBuffer out = ByteBuffer.allocate(4 + 4 + 32).order(ByteOrder.BIG_ENDIAN);
// out.putInt(WireCodes.Status.OK);
// out.putInt(lastNum);
// out.put(hash);
// return out.array();
//
// } catch (Exception e) {
// log.error("GET_LAST_BLOCK_INFO: ошибка", e);
// return intTo4Bytes(WireCodes.Status.INTERNAL_ERROR);
// }
// }
//
// private static byte[] intTo4Bytes(int code) {
// return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(code).array();
// }
//
// private static byte[] hexToBytes(String hex) {
// if (hex == null || hex.isEmpty()) return new byte[32];
// int len = hex.length();
// byte[] out = new byte[len / 2];
// for (int i = 0; i < len; i += 2)
// out[i / 2] = (byte) Integer.parseInt(hex.substring(i, i + 2), 16);
// if (out.length < 32) { // добиваем нулями
// byte[] full = new byte[32];
// System.arraycopy(out, 0, full, 32 - out.length, out.length);
// return full;
// }
// return Arrays.copyOf(out, 32);
// }
//}

View File

@ -1,16 +0,0 @@
package server.logic.ws_protocol.binary.handlers;
import server.logic.ws_protocol.WireCodes;
/**
* Обработчик команды PING.
* Возвращает просто статус PONG.
*/
public class PingHandler implements MessageHandler {
@Override
public byte[] handle(byte[] msg) {
return new byte[]{
0, 0, 0, (byte) WireCodes.Status.PONG // проще и быстрее, можно и через ByteBuffer
};
}
}

View File

@ -1,59 +0,0 @@
//package server.logic.ws_protocol.binary.handlers;
//
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//import server.logic.ws_protocol.WireCodes;
//import utils.search.UserSearchService;
//
//import java.nio.ByteBuffer;
//import java.nio.ByteOrder;
//import java.nio.charset.StandardCharsets;
//import java.util.List;
//
///**
// * Поиск пользователей по логину (SEARCH_USERS).
// */
//public class SearchUsersHandler implements MessageHandler {
// private static final Logger log = LoggerFactory.getLogger(SearchUsersHandler.class);
//
// @Override
// public byte[] handle(byte[] msg) {
// try {
// if (msg.length < 8)
// return intTo4Bytes(WireCodes.Status.BAD_REQUEST);
//
// int N = ByteBuffer.wrap(msg, 4, 4).order(ByteOrder.BIG_ENDIAN).getInt();
// if (N < 0 || msg.length < 8 + N)
// return intTo4Bytes(WireCodes.Status.BAD_REQUEST);
//
// String query = new String(msg, 8, N, StandardCharsets.UTF_8);
// List<UserSearchService.Pair> found = UserSearchService.getInstance().searchFirst5(query);
// return pack(found);
//
// } catch (Exception e) {
// log.error("SEARCH_USERS: ошибка", e);
// return intTo4Bytes(WireCodes.Status.INTERNAL_ERROR);
// }
// }
//
// private static byte[] pack(List<UserSearchService.Pair> pairs) {
// if (pairs == null) pairs = List.of();
// int total = 8;
// var chunks = new java.util.ArrayList<byte[]>();
// for (var p : pairs) {
// byte[] packed = UserSearchService.packPair(p);
// chunks.add(packed);
// total += packed.length;
// }
//
// ByteBuffer out = ByteBuffer.allocate(total).order(ByteOrder.BIG_ENDIAN);
// out.putInt(WireCodes.Status.OK);
// out.putInt(pairs.size());
// for (var c : chunks) out.put(c);
// return out.array();
// }
//
// private static byte[] intTo4Bytes(int code) {
// return ByteBuffer.allocate(4).order(ByteOrder.BIG_ENDIAN).putInt(code).array();
// }
//}

View File

@ -1,5 +1,3 @@
server.1port=7070
db.path=data/shine.sqlite

View File

@ -36,7 +36,7 @@ public class IT_03_AddBlock_NoAuth {
public static void main(String[] args) {
int failed = run();
System.exit(failed);
// System.exit(failed);
}
/** Запуск одного теста (standalone). Возвращает 0 если ок, 1 если упал. */

View File

@ -66,7 +66,7 @@
// === Построение HEADER ===
function buildHeaderBody() {
const tag = textUtf8("SHiNE001");
const tag = textUtf8("SHiNE");
const id = beLong(blockchainId);
const login = textUtf8(userLogin);
const loginLen = new Uint8Array([login.length]);