04 12 25 Версия авторификации где сервер выдовал сессион Ид
This commit is contained in:
parent
fc748a744c
commit
c9bfa2d01a
@ -16,9 +16,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.eclipse.jetty:jetty-server:11.0.20'
|
implementation 'org.eclipse.jetty:jetty-server:11.0.20' // WS сервер
|
||||||
implementation 'org.eclipse.jetty:jetty-servlet:11.0.20'
|
implementation 'org.eclipse.jetty:jetty-servlet:11.0.20'
|
||||||
implementation 'org.eclipse.jetty.websocket:websocket-jetty-server:11.0.20'
|
implementation 'org.eclipse.jetty.websocket:websocket-jetty-server:11.0.20'
|
||||||
|
|
||||||
implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1' // шифрование
|
implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1' // шифрование
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json
|
||||||
|
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import java.net.http.HttpResponse;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Сервис для геолокации по IP.
|
* Сервис для геолокации по IP.
|
||||||
*
|
*.
|
||||||
* Основной метод:
|
* Основной метод:
|
||||||
* resolveCountryCityOrIp(ip) -> "Country, City" или исходный ip, если не удалось.
|
* resolveCountryCityOrIp(ip) -> "Country, City" или исходный ip, если не удалось.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package shine.geo;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Тестовый запуск геолокации.
|
* Тестовый запуск геолокации.
|
||||||
*
|
*.
|
||||||
* Логика:
|
* Логика:
|
||||||
* 1) Если в args[0] передан IP — используем его.
|
* 1) Если в args[0] передан IP — используем его.
|
||||||
* 2) Иначе пробуем узнать внешний IP текущей машины.
|
* 2) Иначе пробуем узнать внешний IP текущей машины.
|
||||||
|
|||||||
@ -16,6 +16,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation 'org.eclipse.jetty:jetty-server:11.0.20' // WS сервер
|
||||||
|
implementation 'org.eclipse.jetty:jetty-servlet:11.0.20'
|
||||||
|
implementation 'org.eclipse.jetty.websocket:websocket-jetty-server:11.0.20'
|
||||||
|
|
||||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json
|
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.1' // json
|
||||||
|
|
||||||
implementation 'org.slf4j:slf4j-api:2.0.9'
|
implementation 'org.slf4j:slf4j-api:2.0.9'
|
||||||
|
|||||||
@ -0,0 +1,123 @@
|
|||||||
|
package server.logic.ws_protocol.JSON;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Реестр активных подключений (только авторизованные).
|
||||||
|
*.
|
||||||
|
* Позволяет:
|
||||||
|
* - получить ConnectionContext по sessionId;
|
||||||
|
* - получить все активные подключения пользователя по loginId;
|
||||||
|
* - удалить подключение при закрытии WebSocket.
|
||||||
|
*.
|
||||||
|
* найти все подключения пользователя:
|
||||||
|
* var set = ActiveConnectionsRegistry.getInstance().getByLoginId(loginId);
|
||||||
|
*.
|
||||||
|
* найти конкретное подключение по sessionId:
|
||||||
|
* ConnectionContext ctx = ActiveConnectionsRegistry.getInstance().getBySessionId(sessionId);
|
||||||
|
* Session ws = ctx != null ? ctx.getWsSession() : null;
|
||||||
|
*/
|
||||||
|
|
||||||
|
public final class ActiveConnectionsRegistry {
|
||||||
|
|
||||||
|
private static final ActiveConnectionsRegistry INSTANCE = new ActiveConnectionsRegistry();
|
||||||
|
|
||||||
|
public static ActiveConnectionsRegistry getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ActiveConnectionsRegistry() {
|
||||||
|
// singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
// sessionId -> ConnectionContext
|
||||||
|
private final ConcurrentHashMap<Long, ConnectionContext> bySessionId = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
// loginId -> множество ConnectionContext для этого пользователя
|
||||||
|
private final ConcurrentHashMap<Long, Set<ConnectionContext>> byLoginId = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Зарегистрировать авторизованное подключение.
|
||||||
|
* Ожидается, что в ctx уже выставлены loginId и sessionId.
|
||||||
|
*/
|
||||||
|
public void register(ConnectionContext ctx) {
|
||||||
|
if (ctx == null) return;
|
||||||
|
|
||||||
|
Long sessionId = ctx.getSessionId();
|
||||||
|
Long loginId = ctx.getLoginId();
|
||||||
|
|
||||||
|
if (sessionId == null || loginId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bySessionId.put(sessionId, ctx);
|
||||||
|
|
||||||
|
byLoginId
|
||||||
|
.computeIfAbsent(loginId, id -> new CopyOnWriteArraySet<>())
|
||||||
|
.add(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удалить подключение по контексту (например, при onClose).
|
||||||
|
*/
|
||||||
|
public void remove(ConnectionContext ctx) {
|
||||||
|
if (ctx == null) return;
|
||||||
|
|
||||||
|
Long sessionId = ctx.getSessionId();
|
||||||
|
Long loginId = ctx.getLoginId();
|
||||||
|
|
||||||
|
if (sessionId != null) {
|
||||||
|
bySessionId.remove(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loginId != null) {
|
||||||
|
Set<ConnectionContext> set = byLoginId.get(loginId);
|
||||||
|
if (set != null) {
|
||||||
|
set.remove(ctx);
|
||||||
|
if (set.isEmpty()) {
|
||||||
|
byLoginId.remove(loginId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удалить подключение по sessionId.
|
||||||
|
*/
|
||||||
|
public void removeBySessionId(long sessionId) {
|
||||||
|
ConnectionContext ctx = bySessionId.remove(sessionId);
|
||||||
|
if (ctx != null) {
|
||||||
|
Long loginId = ctx.getLoginId();
|
||||||
|
if (loginId != null) {
|
||||||
|
Set<ConnectionContext> set = byLoginId.get(loginId);
|
||||||
|
if (set != null) {
|
||||||
|
set.remove(ctx);
|
||||||
|
if (set.isEmpty()) {
|
||||||
|
byLoginId.remove(loginId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить контекст по sessionId.
|
||||||
|
*/
|
||||||
|
public ConnectionContext getBySessionId(long sessionId) {
|
||||||
|
return bySessionId.get(sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получить все активные подключения пользователя по loginId.
|
||||||
|
*/
|
||||||
|
public Set<ConnectionContext> getByLoginId(long loginId) {
|
||||||
|
Set<ConnectionContext> set = byLoginId.get(loginId);
|
||||||
|
if (set == null) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
// CopyOnWriteArraySet безопасно отдавать как есть
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package server.logic.ws_protocol.JSON;
|
package server.logic.ws_protocol.JSON;
|
||||||
|
|
||||||
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import shine.db.entities.SolanaUser;
|
import shine.db.entities.SolanaUser;
|
||||||
import shine.db.entities.ActiveSession;
|
import shine.db.entities.ActiveSession;
|
||||||
|
|
||||||
@ -24,6 +25,22 @@ public class ConnectionContext {
|
|||||||
|
|
||||||
private int authenticationStatus = AUTH_STATUS_NONE;
|
private int authenticationStatus = AUTH_STATUS_NONE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket-сессия Jetty для данного подключения.
|
||||||
|
* Нужна, чтобы через ConnectionContext можно было отправлять сообщения клиенту.
|
||||||
|
*/
|
||||||
|
private Session wsSession;
|
||||||
|
|
||||||
|
// --- WebSocket Session ---
|
||||||
|
|
||||||
|
public Session getWsSession() {
|
||||||
|
return wsSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWsSession(Session wsSession) {
|
||||||
|
this.wsSession = wsSession;
|
||||||
|
}
|
||||||
|
|
||||||
// --- SolanaUser / ActiveSession ---
|
// --- SolanaUser / ActiveSession ---
|
||||||
|
|
||||||
public SolanaUser getSolanaUser() {
|
public SolanaUser getSolanaUser() {
|
||||||
@ -96,6 +113,7 @@ public class ConnectionContext {
|
|||||||
sessionPwd = null;
|
sessionPwd = null;
|
||||||
|
|
||||||
authenticationStatus = AUTH_STATUS_NONE;
|
authenticationStatus = AUTH_STATUS_NONE;
|
||||||
|
wsSession = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -16,7 +16,7 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* JsonHandlerRegistry — единое место, где руками регистрируются
|
* JsonHandlerRegistry — единое место, где руками регистрируются
|
||||||
* JSON-операции: op → handler и op → requestClass.
|
* JSON-операции: op → handler и op → requestClass.
|
||||||
*
|
*.
|
||||||
* Если нужно добавить новый запрос:
|
* Если нужно добавить новый запрос:
|
||||||
* 1) создаёшь класс NetXXXRequest / NetXXXResponse,
|
* 1) создаёшь класс NetXXXRequest / NetXXXResponse,
|
||||||
* 2) создаёшь JsonMessageHandler (NetXXXHandler),
|
* 2) создаёшь JsonMessageHandler (NetXXXHandler),
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* JsonInboundProcessor — обработка JSON-сообщений.
|
* JsonInboundProcessor — обработка JSON-сообщений.
|
||||||
*
|
*.
|
||||||
* 1) Парсит общий пакет (op, requestId,...).
|
* 1) Парсит общий пакет (op, requestId,...).
|
||||||
* 2) По op выбирает класс запроса и хэндлер.
|
* 2) По op выбирает класс запроса и хэндлер.
|
||||||
* 3) Маппит JSON → NetRequest через ObjectMapper.
|
* 3) Маппит JSON → NetRequest через ObjectMapper.
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Шаг 2 авторизации: клиент подтверждает владение ключом.
|
* Шаг 2 авторизации: клиент подтверждает владение ключом.
|
||||||
*
|
*.
|
||||||
* JSON:
|
* JSON:
|
||||||
* {
|
* {
|
||||||
* "op": "AuthSessionNewStep2",
|
* "op": "AuthSessionNewStep2",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Ответ на AuthSessionNewStep2.
|
* Ответ на AuthSessionNewStep2.
|
||||||
*
|
*.
|
||||||
* Успешный JSON:
|
* Успешный JSON:
|
||||||
* {
|
* {
|
||||||
* "op": "AuthSessionNewStep2",
|
* "op": "AuthSessionNewStep2",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Запрос SessionRefresh.
|
* Запрос SessionRefresh.
|
||||||
*
|
*.
|
||||||
* JSON (payload):
|
* JSON (payload):
|
||||||
* {
|
* {
|
||||||
* "sessionId": 123,
|
* "sessionId": 123,
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Успешный ответ на SessionRefresh.
|
* Успешный ответ на SessionRefresh.
|
||||||
*
|
*.
|
||||||
* Дополнительных полей нет, достаточно status=200 и (опционально) пустого payload.
|
* Дополнительных полей нет, достаточно status=200 и (опционально) пустого payload.
|
||||||
*/
|
*/
|
||||||
public class NetSessionRefreshResponse extends NetResponse {
|
public class NetSessionRefreshResponse extends NetResponse {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package server.logic.ws_protocol.JSON.entyties;
|
|||||||
/**
|
/**
|
||||||
* Базовый класс для всех событий (event).
|
* Базовый класс для всех событий (event).
|
||||||
* Общие поля: op и payload.
|
* Общие поля: op и payload.
|
||||||
*
|
*.
|
||||||
* Формат JSON (event):
|
* Формат JSON (event):
|
||||||
* {
|
* {
|
||||||
* "op": "...",
|
* "op": "...",
|
||||||
|
|||||||
@ -2,7 +2,7 @@ package server.logic.ws_protocol.JSON.entyties;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Ответ с ошибкой (любой отказ).
|
* Ответ с ошибкой (любой отказ).
|
||||||
*
|
*.
|
||||||
* В payload будет:
|
* В payload будет:
|
||||||
* {
|
* {
|
||||||
* "code": "...",
|
* "code": "...",
|
||||||
|
|||||||
@ -2,9 +2,9 @@ package server.logic.ws_protocol.JSON.entyties;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Базовый класс для всех запросов (client → server).
|
* Базовый класс для всех запросов (client → server).
|
||||||
*
|
*.
|
||||||
* Наследуется от NetEvent и добавляет requestId.
|
* Наследуется от NetEvent и добавляет requestId.
|
||||||
*
|
*.
|
||||||
* Формат JSON (request):
|
* Формат JSON (request):
|
||||||
* {
|
* {
|
||||||
* "op": "...",
|
* "op": "...",
|
||||||
|
|||||||
@ -2,9 +2,9 @@ package server.logic.ws_protocol.JSON.entyties;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Базовый класс для всех ответов (server → client).
|
* Базовый класс для всех ответов (server → client).
|
||||||
*
|
*.
|
||||||
* Наследуется от NetRequest и добавляет status.
|
* Наследуется от NetRequest и добавляет status.
|
||||||
*
|
*.
|
||||||
* Формат JSON (response):
|
* Формат JSON (response):
|
||||||
* {
|
* {
|
||||||
* "op": "...",
|
* "op": "...",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Запрос AddUser.
|
* Запрос AddUser.
|
||||||
*
|
*.
|
||||||
* Ожидаемый JSON:
|
* Ожидаемый JSON:
|
||||||
* {
|
* {
|
||||||
* "op": "AddUser",
|
* "op": "AddUser",
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package server.logic.ws_protocol.JSON.handlers.auth;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
|
||||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||||
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
||||||
@ -22,7 +23,7 @@ import java.util.concurrent.ThreadLocalRandom;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Шаг 2 авторизации: проверка подписи и создание сессии.
|
* Шаг 2 авторизации: проверка подписи и создание сессии.
|
||||||
*
|
*.
|
||||||
* Клиент присылает:
|
* Клиент присылает:
|
||||||
* - loginId
|
* - loginId
|
||||||
* - sigNum (0 или 1)
|
* - sigNum (0 или 1)
|
||||||
@ -165,6 +166,9 @@ public class NetAuthSessionNewStep2Handler implements JsonMessageHandler {
|
|||||||
ctx.setSessionId(sessionId);
|
ctx.setSessionId(sessionId);
|
||||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
||||||
|
|
||||||
|
// Регистрируем это подключение в глобальном реестре активных соединений
|
||||||
|
ActiveConnectionsRegistry.getInstance().register(ctx);
|
||||||
|
|
||||||
// --- формируем ответ ---
|
// --- формируем ответ ---
|
||||||
NetAuthSessionNewStep2Response resp = new NetAuthSessionNewStep2Response();
|
NetAuthSessionNewStep2Response resp = new NetAuthSessionNewStep2Response();
|
||||||
resp.setOp(req.getOp());
|
resp.setOp(req.getOp());
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package server.logic.ws_protocol.JSON.handlers.auth;
|
|||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
|
||||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||||
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
import server.logic.ws_protocol.JSON.entyties.NetRequest;
|
||||||
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
import server.logic.ws_protocol.JSON.entyties.NetResponse;
|
||||||
@ -108,6 +109,9 @@ public class NetSessionRefreshHandler implements JsonMessageHandler {
|
|||||||
ctx.setSessionId(sessionId);
|
ctx.setSessionId(sessionId);
|
||||||
ctx.setSessionPwd(sessionPwd);
|
ctx.setSessionPwd(sessionPwd);
|
||||||
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
ctx.setAuthenticationStatus(ConnectionContext.AUTH_STATUS_USER);
|
||||||
|
|
||||||
|
// Регистрируем это подключение в глобальном реестре активных соединений
|
||||||
|
ActiveConnectionsRegistry.getInstance().register(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
// И возвращаем OK без доп. полей (payload будет {}).
|
// И возвращаем OK без доп. полей (payload будет {}).
|
||||||
|
|||||||
@ -2,11 +2,11 @@ package server.logic.ws_protocol;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* WireCodes — константы бинарного протокола поверх WebSocket.
|
* WireCodes — константы бинарного протокола поверх WebSocket.
|
||||||
*
|
*.
|
||||||
* Формат входящего сообщения:
|
* Формат входящего сообщения:
|
||||||
* [4] int opCode (big-endian)
|
* [4] int opCode (big-endian)
|
||||||
* [*] payload
|
* [*] payload
|
||||||
*
|
*.
|
||||||
* Ответ сервера:
|
* Ответ сервера:
|
||||||
* ровно [4] int statusCode (big-endian)
|
* ровно [4] int statusCode (big-endian)
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
* ============================================================================
|
* ============================================================================
|
||||||
* BchBlockEntry — универсальная запись блокчейна SHiNE (.bch)
|
* BchBlockEntry — универсальная запись блокчейна SHiNE (.bch)
|
||||||
* ============================================================================
|
* ============================================================================
|
||||||
*
|
*.
|
||||||
* 🧩 Формат файла .bch:
|
* 🧩 Формат файла .bch:
|
||||||
* Каждый блок хранится последовательно, без промежутков.
|
* Каждый блок хранится последовательно, без промежутков.
|
||||||
* Один блок = «заголовок» (RAW) + подпись (64) + хэш (32).
|
* Один блок = «заголовок» (RAW) + подпись (64) + хэш (32).
|
||||||
*
|
*.
|
||||||
* FULL = RAW + signature(64) + hash(32)
|
* FULL = RAW + signature(64) + hash(32)
|
||||||
*
|
*.
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* 🔹 Структура RAW-части блока (без подписи и хэша)
|
* 🔹 Структура RAW-части блока (без подписи и хэша)
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* Размеры и порядок строго фиксированы (BigEndian).
|
* Размеры и порядок строго фиксированы (BigEndian).
|
||||||
*
|
*.
|
||||||
* Порядок байтов (сверху вниз, смещения от начала RAW):
|
* Порядок байтов (сверху вниз, смещения от начала RAW):
|
||||||
*
|
*.
|
||||||
* ┌────────────────────────────┬────────┬───────────────────────────────┐
|
* ┌────────────────────────────┬────────┬───────────────────────────────┐
|
||||||
* │ Поле │ Размер │ Описание │
|
* │ Поле │ Размер │ Описание │
|
||||||
* ├────────────────────────────┼────────┼───────────────────────────────┤
|
* ├────────────────────────────┼────────┼───────────────────────────────┤
|
||||||
@ -30,22 +30,22 @@
|
|||||||
* │ recordTypeVersion │ 2 байта│ версия структуры данного типа │
|
* │ recordTypeVersion │ 2 байта│ версия структуры данного типа │
|
||||||
* │ body │ M байт │ бинарное тело записи │
|
* │ body │ M байт │ бинарное тело записи │
|
||||||
* └────────────────────────────┴────────┴───────────────────────────────┘
|
* └────────────────────────────┴────────┴───────────────────────────────┘
|
||||||
*
|
*.
|
||||||
* ⇒ RAW_HEADER_SIZE = 4 + 4 + 8 + 2 + 2 = 20 байт.
|
* ⇒ RAW_HEADER_SIZE = 4 + 4 + 8 + 2 + 2 = 20 байт.
|
||||||
* ⇒ recordSize = RAW_HEADER_SIZE + body.length
|
* ⇒ recordSize = RAW_HEADER_SIZE + body.length
|
||||||
*
|
*.
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* 🔹 Структура FULL-блока
|
* 🔹 Структура FULL-блока
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
*
|
*.
|
||||||
* ┌────────────────────────────┬─────────┬──────────────────────────────┐
|
* ┌────────────────────────────┬─────────┬──────────────────────────────┐
|
||||||
* │ RAW │ M+20 │ тело блока без подписи │
|
* │ RAW │ M+20 │ тело блока без подписи │
|
||||||
* │ signature64 │ 64 │ подпись Ed25519(preimage) │
|
* │ signature64 │ 64 │ подпись Ed25519(preimage) │
|
||||||
* │ hash32 │ 32 │ SHA-256(preimage) │
|
* │ hash32 │ 32 │ SHA-256(preimage) │
|
||||||
* └────────────────────────────┴─────────┴──────────────────────────────┘
|
* └────────────────────────────┴─────────┴──────────────────────────────┘
|
||||||
*
|
*.
|
||||||
* ⇒ Общая длина FULL = recordSize + 96 байт.
|
* ⇒ Общая длина FULL = recordSize + 96 байт.
|
||||||
*
|
*.
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
* 🔹 Канонический preimage для подписи/хэша
|
* 🔹 Канонический preimage для подписи/хэша
|
||||||
* ---------------------------------------------------------------------------
|
* ---------------------------------------------------------------------------
|
||||||
@ -58,9 +58,9 @@
|
|||||||
* можно номер блока?
|
* можно номер блока?
|
||||||
* prevHash32(32B) +
|
* prevHash32(32B) +
|
||||||
* rawBytes (M+20B)
|
* rawBytes (M+20B)
|
||||||
*
|
*.
|
||||||
* hash32 = SHA-256(preimage)
|
* hash32 = SHA-256(preimage)
|
||||||
* signature64= Ed25519.sign(preimage, privateKey)
|
* signature64= Ed25519.sign(preimage, privateKey)
|
||||||
*
|
*.
|
||||||
* Проверка осуществляется через {@link utils.crypto.BchCryptoVerifier}.
|
* Проверка осуществляется через {@link utils.crypto.BchCryptoVerifier}.
|
||||||
|
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import java.util.Arrays;
|
|||||||
* AddBlockHandler — обработчик команды "добавить блок" (ADD_BLOCK)
|
* AddBlockHandler — обработчик команды "добавить блок" (ADD_BLOCK)
|
||||||
* ---------------------------------------------------------------
|
* ---------------------------------------------------------------
|
||||||
* Принимает бинарное сообщение от клиента и добавляет новый блок в цепочку.
|
* Принимает бинарное сообщение от клиента и добавляет новый блок в цепочку.
|
||||||
*
|
*.
|
||||||
* Формат входного сообщения (msg):
|
* Формат входного сообщения (msg):
|
||||||
* [0..3] — 4 байта: код операции (WireCodes.ADD_BLOCK)
|
* [0..3] — 4 байта: код операции (WireCodes.ADD_BLOCK)
|
||||||
* [4..11] — 8 байт: blockchainId (уникальный идентификатор цепочки)
|
* [4..11] — 8 байт: blockchainId (уникальный идентификатор цепочки)
|
||||||
@ -33,13 +33,13 @@ import java.util.Arrays;
|
|||||||
* ├── M байт body (содержимое блока)
|
* ├── M байт body (содержимое блока)
|
||||||
* ├── 64 байта signature (Ed25519)
|
* ├── 64 байта signature (Ed25519)
|
||||||
* └── 32 байта hash (SHA-256)
|
* └── 32 байта hash (SHA-256)
|
||||||
*
|
*.
|
||||||
* ---------------------------------------------------------------
|
* ---------------------------------------------------------------
|
||||||
* Алгоритм работы:
|
* Алгоритм работы:
|
||||||
*
|
*.
|
||||||
* 1️⃣ Распаковать BchBlockEntry из msg (т.е. выделить тело блока и подписи).
|
* 1️⃣ Распаковать BchBlockEntry из msg (т.е. выделить тело блока и подписи).
|
||||||
* 2️⃣ Найти описание цепочки (BchInfoEntry) по blockchainId.
|
* 2️⃣ Найти описание цепочки (BchInfoEntry) по blockchainId.
|
||||||
*
|
*.
|
||||||
* ─ Если описания нет (цепочка ещё не существует):
|
* ─ Если описания нет (цепочка ещё не существует):
|
||||||
* • принимаем только блок типа 0 (HeaderBody) и номера 0;
|
* • принимаем только блок типа 0 (HeaderBody) и номера 0;
|
||||||
* • парсим его, создаём новый BchInfoEntry на основе данных заголовка;
|
* • парсим его, создаём новый BchInfoEntry на основе данных заголовка;
|
||||||
@ -48,16 +48,16 @@ import java.util.Arrays;
|
|||||||
* • сохраняем блок и создаём новый blockchain-файл;
|
* • сохраняем блок и создаём новый blockchain-файл;
|
||||||
* • добавляем цепочку в менеджер BchInfoManager.
|
* • добавляем цепочку в менеджер BchInfoManager.
|
||||||
* (💡 временное решение: создание цепочки допустимо только через HeaderBody)
|
* (💡 временное решение: создание цепочки допустимо только через HeaderBody)
|
||||||
*
|
*.
|
||||||
* ─ Если цепочка уже существует:
|
* ─ Если цепочка уже существует:
|
||||||
* • проверяем, что номер блока равен (lastBlockNumber + 1);
|
* • проверяем, что номер блока равен (lastBlockNumber + 1);
|
||||||
* • проверяем подпись и хэш;
|
* • проверяем подпись и хэш;
|
||||||
* • проверяем тело блока (check);
|
* • проверяем тело блока (check);
|
||||||
* • добавляем блок в файл цепочки;
|
* • добавляем блок в файл цепочки;
|
||||||
* • обновляем состояние BchInfoEntry (номер, хэш, размер).
|
* • обновляем состояние BchInfoEntry (номер, хэш, размер).
|
||||||
*
|
*.
|
||||||
* 3️⃣ Если все проверки пройдены — возвращаем статус OK.
|
* 3️⃣ Если все проверки пройдены — возвращаем статус OK.
|
||||||
*
|
*.
|
||||||
* Таким образом, единственное различие между первым блоком и последующими —
|
* Таким образом, единственное различие между первым блоком и последующими —
|
||||||
* момент инициализации описания цепочки (BchInfoEntry).
|
* момент инициализации описания цепочки (BchInfoEntry).
|
||||||
* Всё остальное (валидация, подпись, добавление, обновление) выполняется одинаково.
|
* Всё остальное (валидация, подпись, добавление, обновление) выполняется одинаково.
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import org.eclipse.jetty.websocket.api.annotations.*;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import server.logic.InboundMessageProcessor;
|
import server.logic.InboundMessageProcessor;
|
||||||
|
import server.logic.ws_protocol.JSON.ActiveConnectionsRegistry;
|
||||||
import server.logic.ws_protocol.JSON.ConnectionContext;
|
import server.logic.ws_protocol.JSON.ConnectionContext;
|
||||||
import server.logic.ws_protocol.JSON.JsonInboundProcessor;
|
import server.logic.ws_protocol.JSON.JsonInboundProcessor;
|
||||||
|
|
||||||
@ -24,6 +25,8 @@ public class BlockchainWsEndpoint {
|
|||||||
@OnWebSocketConnect
|
@OnWebSocketConnect
|
||||||
public void onConnect(Session session) {
|
public void onConnect(Session session) {
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
// Привязываем WebSocket-сессию к ConnectionContext
|
||||||
|
connectionContext.setWsSession(session);
|
||||||
log.info("WS connected: {}", session.getRemoteAddress());
|
log.info("WS connected: {}", session.getRemoteAddress());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +80,8 @@ public class BlockchainWsEndpoint {
|
|||||||
@OnWebSocketClose
|
@OnWebSocketClose
|
||||||
public void onClose(int statusCode, String reason) {
|
public void onClose(int statusCode, String reason) {
|
||||||
log.info("WS closed: {} {}", statusCode, reason);
|
log.info("WS closed: {} {}", statusCode, reason);
|
||||||
|
// Удаляем это подключение из реестра активных соединений
|
||||||
|
ActiveConnectionsRegistry.getInstance().remove(connectionContext);
|
||||||
// На всякий случай очищаем контекст
|
// На всякий случай очищаем контекст
|
||||||
connectionContext.reset();
|
connectionContext.reset();
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user