From 4e14f300f9aa4a2dbe8d2ad006787f737fd5d0811ee018fa3703f652ac9a8af2 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Wed, 24 Dec 2025 17:11:29 +0300 Subject: [PATCH] =?UTF-8?q?24=2012=2025=20=D0=94=D0=BE=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=B0=D1=82=D1=8B=D0=B2=D0=B0=D1=8E=20=D0=B4=D0=BE=D0=B1=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B1=D0=BB=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2.=20=D0=A3=D0=B1=D1=80=D0=B0=D0=BB=20=D0=BB=D0=B8?= =?UTF-8?q?=D1=88=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B0=D1=80=D1=8B=D0=B5?= =?UTF-8?q?=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/utils/blockchain/BchInfoEntry.java | 124 +++--- .../java/utils/blockchain/BchInfoManager.java | 356 +++++++++--------- .../utils/blockchain/BlockchainNameUtil.java | 1 + .../src/main/java/utils/blockchain/TODO.md | 24 -- .../java/utils/search/UserSearchService.java | 140 +++---- 5 files changed, 311 insertions(+), 334 deletions(-) delete mode 100644 shine-server-blockchain/src/main/java/utils/blockchain/TODO.md diff --git a/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoEntry.java b/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoEntry.java index c48a988..aac709c 100644 --- a/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoEntry.java +++ b/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoEntry.java @@ -1,62 +1,62 @@ -package utils.blockchain; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import java.util.Base64; - -/** - * BchInfoEntry — данные об одной цепочке блокчейна. - * Используется менеджером BchInfoManager. - */ -public final class BchInfoEntry { - - @JsonProperty("blockchainId") - public final long blockchainId; - - @JsonProperty("userLogin") - public final String userLogin; - - @JsonProperty("publicKeyBase64") - public final String publicKeyBase64; - - @JsonProperty("blockchainSizeLimit") - public final int blockchainSizeLimit; - - @JsonProperty("blockchainSize") - public final int blockchainSize; - - @JsonProperty("lastBlockNumber") - public final int lastBlockNumber; - - @JsonProperty("lastBlockHash") - public final String lastBlockHash; - - @JsonCreator - public BchInfoEntry( - @JsonProperty("blockchainId") long blockchainId, - @JsonProperty("userLogin") String userLogin, - @JsonProperty("publicKeyBase64") String publicKeyBase64, - @JsonProperty("blockchainSizeLimit") int blockchainSizeLimit, - @JsonProperty("blockchainSize") int blockchainSize, - @JsonProperty("lastBlockNumber") int lastBlockNumber, - @JsonProperty("lastBlockHash") String lastBlockHash - ) { - this.blockchainId = blockchainId; - this.userLogin = userLogin == null ? "" : userLogin; - this.publicKeyBase64 = publicKeyBase64; - this.blockchainSizeLimit = blockchainSizeLimit; - this.blockchainSize = blockchainSize; - this.lastBlockNumber = lastBlockNumber; - this.lastBlockHash = lastBlockHash == null ? "" : lastBlockHash; - } - - /** Публичный ключ в бинарном виде (32 байта) или null, если битый. */ - public byte[] getPublicKey32() { - try { - byte[] raw = Base64.getDecoder().decode(publicKeyBase64); - return (raw != null && raw.length == 32) ? raw : null; - } catch (IllegalArgumentException e) { - return null; - } - } -} +//package utils.blockchain; +// +//import com.fasterxml.jackson.annotation.JsonCreator; +//import com.fasterxml.jackson.annotation.JsonProperty; +//import java.util.Base64; +// +///** +// * BchInfoEntry — данные об одной цепочке блокчейна. +// * Используется менеджером BchInfoManager. +// */ +//public final class BchInfoEntry { +// +// @JsonProperty("blockchainId") +// public final long blockchainId; +// +// @JsonProperty("userLogin") +// public final String userLogin; +// +// @JsonProperty("publicKeyBase64") +// public final String publicKeyBase64; +// +// @JsonProperty("blockchainSizeLimit") +// public final int blockchainSizeLimit; +// +// @JsonProperty("blockchainSize") +// public final int blockchainSize; +// +// @JsonProperty("lastBlockNumber") +// public final int lastBlockNumber; +// +// @JsonProperty("lastBlockHash") +// public final String lastBlockHash; +// +// @JsonCreator +// public BchInfoEntry( +// @JsonProperty("blockchainId") long blockchainId, +// @JsonProperty("userLogin") String userLogin, +// @JsonProperty("publicKeyBase64") String publicKeyBase64, +// @JsonProperty("blockchainSizeLimit") int blockchainSizeLimit, +// @JsonProperty("blockchainSize") int blockchainSize, +// @JsonProperty("lastBlockNumber") int lastBlockNumber, +// @JsonProperty("lastBlockHash") String lastBlockHash +// ) { +// this.blockchainId = blockchainId; +// this.userLogin = userLogin == null ? "" : userLogin; +// this.publicKeyBase64 = publicKeyBase64; +// this.blockchainSizeLimit = blockchainSizeLimit; +// this.blockchainSize = blockchainSize; +// this.lastBlockNumber = lastBlockNumber; +// this.lastBlockHash = lastBlockHash == null ? "" : lastBlockHash; +// } +// +// /** Публичный ключ в бинарном виде (32 байта) или null, если битый. */ +// public byte[] getPublicKey32() { +// try { +// byte[] raw = Base64.getDecoder().decode(publicKeyBase64); +// return (raw != null && raw.length == 32) ? raw : null; +// } catch (IllegalArgumentException e) { +// return null; +// } +// } +//} diff --git a/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoManager.java b/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoManager.java index 1772c53..f4b7530 100644 --- a/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoManager.java +++ b/shine-server-blockchain/src/main/java/utils/blockchain/BchInfoManager.java @@ -1,178 +1,178 @@ -package utils.blockchain; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.nio.file.*; -import java.util.Base64; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * BchInfoManager — Singleton. - *. - * Держит в памяти информацию обо всех блокчейнах. - * Сейчас читает и пишет JSON на диск (data/blockchain_info.json). - * В будущем можно заменить на SQL без изменений в остальном коде. - */ -public final class BchInfoManager { - - private static final Logger log = LoggerFactory.getLogger(BchInfoManager.class); - - private static final String FILE_NAME = "blockchain_info.json"; - private static final Path DATA_DIR = Paths.get("data"); - private static final ObjectMapper MAPPER = new ObjectMapper(); - - private static BchInfoManager instance; - - /** blockchainId → запись о цепочке */ - private final Map records = new LinkedHashMap<>(); - private final Path path = DATA_DIR.resolve(FILE_NAME); - - private BchInfoManager() { - ensureDataDir(); - load(); - } - - public static synchronized BchInfoManager getInstance() { - if (instance == null) instance = new BchInfoManager(); - return instance; - } - - // ========== API ========== - - /** Создать новую цепочку (после первого HEADER-блока). */ - public synchronized void addBlockchain(long blockchainId, - String userLogin, - byte[] publicKey32, - int blockchainSizeLimit) { - if (publicKey32 == null || publicKey32.length != 32) - throw new IllegalArgumentException("publicKey32 must be 32 bytes"); - if (records.containsKey(blockchainId)) - throw new IllegalArgumentException("blockchain already exists: " + blockchainId); - - BchInfoEntry entry = new BchInfoEntry( - blockchainId, - userLogin, - Base64.getEncoder().encodeToString(publicKey32), - blockchainSizeLimit, - 0, 0, "" - ); - - records.put(blockchainId, entry); - log.info("Добавлен блокчейн id={} login='{}' (лимит {})", blockchainId, userLogin, blockchainSizeLimit); - save(); - } - - /** Обновить состояние после добавления нового блока. */ - public synchronized void updateBlockchainState(long blockchainId, - int lastBlockNumber, - String lastBlockHash, - int blockchainSize) { - BchInfoEntry prev = getEntryOrThrow(blockchainId); - - BchInfoEntry updated = new BchInfoEntry( - prev.blockchainId, - prev.userLogin, - prev.publicKeyBase64, - prev.blockchainSizeLimit, - blockchainSize, - lastBlockNumber, - lastBlockHash - ); - - records.put(blockchainId, updated); - log.info("Обновлено состояние id={} lastNum={} hash={} size={}", - blockchainId, lastBlockNumber, lastBlockHash, blockchainSize); - save(); - } - - /** Получить полную информацию по blockchainId. */ - public synchronized BchInfoEntry getBchInfo(long blockchainId) { - return records.get(blockchainId); - } - - /** Быстро проверить существование цепочки. */ - public synchronized boolean exists(long blockchainId) { - return records.containsKey(blockchainId); - } - - /** id → userLogin (для поиска пользователей). */ - public synchronized Map getAllLoginsSnapshot() { - Map copy = new LinkedHashMap<>(records.size()); - for (var e : records.entrySet()) { - copy.put(e.getKey(), e.getValue().userLogin); - } - return copy; - } - - // ========== private ========== - - private BchInfoEntry getEntryOrThrow(long blockchainId) { - BchInfoEntry e = records.get(blockchainId); - if (e == null) throw new IllegalStateException("Блокчейн с id=" + blockchainId + " не найден."); - return e; - } - - private void ensureDataDir() { - try { - if (!Files.exists(DATA_DIR)) { - Files.createDirectories(DATA_DIR); - log.info("Создана директория данных: {}", DATA_DIR); - } - } catch (IOException e) { - throw new IllegalStateException("Не удалось создать директорию хранения: " + DATA_DIR, e); - } - } - - private synchronized void load() { - if (!Files.exists(path)) { - save(); - log.info("Создан JSON-хранилище: {}", path); - return; - } - try { - byte[] json = Files.readAllBytes(path); - if (json.length == 0) return; - - Map map = MAPPER.readValue( - json, - MAPPER.getTypeFactory().constructMapType(Map.class, String.class, BchInfoEntry.class) - ); - - records.clear(); - for (var e : map.entrySet()) { - try { - long id = Long.parseLong(e.getKey()); - records.put(id, e.getValue()); - } catch (NumberFormatException nfe) { - log.warn("Пропущен некорректный ключ '{}' в JSON", e.getKey()); - } - } - log.info("Загружено {} записей из {}", records.size(), path); - } catch (IOException e) { - log.error("Ошибка загрузки {}", path, e); - } - } - - /** Атомарная запись JSON через .tmp + ATOMIC_MOVE */ - private synchronized void save() { - try { - Map map = new LinkedHashMap<>(); - for (var e : records.entrySet()) - map.put(String.valueOf(e.getKey()), e.getValue()); - - byte[] json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsBytes(map); - - Path tmp = path.resolveSibling(FILE_NAME + ".tmp"); - Files.write(tmp, json, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - Files.move(tmp, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - - log.debug("Сохранено {} записей в {}", records.size(), path); - } catch (IOException e) { - log.error("Ошибка сохранения {}", path, e); - } - } -} +//package utils.blockchain; +// +//import com.fasterxml.jackson.databind.ObjectMapper; +//import org.slf4j.Logger; +//import org.slf4j.LoggerFactory; +// +//import java.io.IOException; +//import java.nio.file.*; +//import java.util.Base64; +//import java.util.LinkedHashMap; +//import java.util.Map; +// +///** +// * BchInfoManager — Singleton. +// *. +// * Держит в памяти информацию обо всех блокчейнах. +// * Сейчас читает и пишет JSON на диск (data/blockchain_info.json). +// * В будущем можно заменить на SQL без изменений в остальном коде. +// */ +//public final class BchInfoManager { +// +// private static final Logger log = LoggerFactory.getLogger(BchInfoManager.class); +// +// private static final String FILE_NAME = "blockchain_info.json"; +// private static final Path DATA_DIR = Paths.get("data"); +// private static final ObjectMapper MAPPER = new ObjectMapper(); +// +// private static BchInfoManager instance; +// +// /** blockchainId → запись о цепочке */ +// private final Map records = new LinkedHashMap<>(); +// private final Path path = DATA_DIR.resolve(FILE_NAME); +// +// private BchInfoManager() { +// ensureDataDir(); +// load(); +// } +// +// public static synchronized BchInfoManager getInstance() { +// if (instance == null) instance = new BchInfoManager(); +// return instance; +// } +// +// // ========== API ========== +// +// /** Создать новую цепочку (после первого HEADER-блока). */ +// public synchronized void addBlockchain(long blockchainId, +// String userLogin, +// byte[] publicKey32, +// int blockchainSizeLimit) { +// if (publicKey32 == null || publicKey32.length != 32) +// throw new IllegalArgumentException("publicKey32 must be 32 bytes"); +// if (records.containsKey(blockchainId)) +// throw new IllegalArgumentException("blockchain already exists: " + blockchainId); +// +// BchInfoEntry entry = new BchInfoEntry( +// blockchainId, +// userLogin, +// Base64.getEncoder().encodeToString(publicKey32), +// blockchainSizeLimit, +// 0, 0, "" +// ); +// +// records.put(blockchainId, entry); +// log.info("Добавлен блокчейн id={} login='{}' (лимит {})", blockchainId, userLogin, blockchainSizeLimit); +// save(); +// } +// +// /** Обновить состояние после добавления нового блока. */ +// public synchronized void updateBlockchainState(long blockchainId, +// int lastBlockNumber, +// String lastBlockHash, +// int blockchainSize) { +// BchInfoEntry prev = getEntryOrThrow(blockchainId); +// +// BchInfoEntry updated = new BchInfoEntry( +// prev.blockchainId, +// prev.userLogin, +// prev.publicKeyBase64, +// prev.blockchainSizeLimit, +// blockchainSize, +// lastBlockNumber, +// lastBlockHash +// ); +// +// records.put(blockchainId, updated); +// log.info("Обновлено состояние id={} lastNum={} hash={} size={}", +// blockchainId, lastBlockNumber, lastBlockHash, blockchainSize); +// save(); +// } +// +// /** Получить полную информацию по blockchainId. */ +// public synchronized BchInfoEntry getBchInfo(long blockchainId) { +// return records.get(blockchainId); +// } +// +// /** Быстро проверить существование цепочки. */ +// public synchronized boolean exists(long blockchainId) { +// return records.containsKey(blockchainId); +// } +// +// /** id → userLogin (для поиска пользователей). */ +// public synchronized Map getAllLoginsSnapshot() { +// Map copy = new LinkedHashMap<>(records.size()); +// for (var e : records.entrySet()) { +// copy.put(e.getKey(), e.getValue().userLogin); +// } +// return copy; +// } +// +// // ========== private ========== +// +// private BchInfoEntry getEntryOrThrow(long blockchainId) { +// BchInfoEntry e = records.get(blockchainId); +// if (e == null) throw new IllegalStateException("Блокчейн с id=" + blockchainId + " не найден."); +// return e; +// } +// +// private void ensureDataDir() { +// try { +// if (!Files.exists(DATA_DIR)) { +// Files.createDirectories(DATA_DIR); +// log.info("Создана директория данных: {}", DATA_DIR); +// } +// } catch (IOException e) { +// throw new IllegalStateException("Не удалось создать директорию хранения: " + DATA_DIR, e); +// } +// } +// +// private synchronized void load() { +// if (!Files.exists(path)) { +// save(); +// log.info("Создан JSON-хранилище: {}", path); +// return; +// } +// try { +// byte[] json = Files.readAllBytes(path); +// if (json.length == 0) return; +// +// Map map = MAPPER.readValue( +// json, +// MAPPER.getTypeFactory().constructMapType(Map.class, String.class, BchInfoEntry.class) +// ); +// +// records.clear(); +// for (var e : map.entrySet()) { +// try { +// long id = Long.parseLong(e.getKey()); +// records.put(id, e.getValue()); +// } catch (NumberFormatException nfe) { +// log.warn("Пропущен некорректный ключ '{}' в JSON", e.getKey()); +// } +// } +// log.info("Загружено {} записей из {}", records.size(), path); +// } catch (IOException e) { +// log.error("Ошибка загрузки {}", path, e); +// } +// } +// +// /** Атомарная запись JSON через .tmp + ATOMIC_MOVE */ +// private synchronized void save() { +// try { +// Map map = new LinkedHashMap<>(); +// for (var e : records.entrySet()) +// map.put(String.valueOf(e.getKey()), e.getValue()); +// +// byte[] json = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsBytes(map); +// +// Path tmp = path.resolveSibling(FILE_NAME + ".tmp"); +// Files.write(tmp, json, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); +// Files.move(tmp, path, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); +// +// log.debug("Сохранено {} записей в {}", records.size(), path); +// } catch (IOException e) { +// log.error("Ошибка сохранения {}", path, e); +// } +// } +//} diff --git a/shine-server-blockchain/src/main/java/utils/blockchain/BlockchainNameUtil.java b/shine-server-blockchain/src/main/java/utils/blockchain/BlockchainNameUtil.java index ba34146..d8a315a 100644 --- a/shine-server-blockchain/src/main/java/utils/blockchain/BlockchainNameUtil.java +++ b/shine-server-blockchain/src/main/java/utils/blockchain/BlockchainNameUtil.java @@ -3,6 +3,7 @@ package utils.blockchain; public final class BlockchainNameUtil { /** Сколько символов отрезаем с конца blockchainName, чтобы получить login. */ + /** Теперь новое правило везде использовать только 3 символа номера блокчена в конце его имени */ public static final int BLOCKCHAIN_NAME_LOGIN_SUFFIX_LEN = 3; private BlockchainNameUtil() {} diff --git a/shine-server-blockchain/src/main/java/utils/blockchain/TODO.md b/shine-server-blockchain/src/main/java/utils/blockchain/TODO.md deleted file mode 100644 index eddaef0..0000000 --- a/shine-server-blockchain/src/main/java/utils/blockchain/TODO.md +++ /dev/null @@ -1,24 +0,0 @@ -# TODO - -1. Проверка перед созданием нового блокчейна - -Сейчас: -- Метод `BlockchainIdInfo.addBlockchain(...)` просто добавляет новую цепочку (blockchainId + userLogin + publicKey32) в локальное хранилище. -- Любой первый блок (HEADER, recordNumber = 0) с валидной подписью автоматически создаёт новую запись. - -Что нужно сделать в будущем: -- Перед созданием новой цепочки проверять, что этот пользователь реально зарегистрирован в системе и имеет право открыть блокчейн. -- Проверять, что `userLogin` и `publicKey32` совпадают с тем, что у нас уже привязано к этому пользователю. -- Если пользователь не найден или ключ не совпадает — отказ, цепочку не создавать. - -Идея: `handleAddBlock(...)` должен вызывать будущий Auth/Users сервис до `addBlockchain(...)`. - - -2. Перенос хранения из файлов в базу SQL - -Сейчас: -- Блоки пишутся в файл `data/.bch` через `FileStoreUtil`. -- Метаданные по цепочке (логин, публичный ключ, последний номер блока, последний hash, размер и т.д.) хранятся в `data/blockchain_id_info.json` через `BlockchainIdInfo`. - -Что нужно сделать: -- Убрать файловое хранение и перейти на SQL. \ No newline at end of file diff --git a/shine-server-blockchain/src/main/java/utils/search/UserSearchService.java b/shine-server-blockchain/src/main/java/utils/search/UserSearchService.java index 6a7812a..3a0d859 100644 --- a/shine-server-blockchain/src/main/java/utils/search/UserSearchService.java +++ b/shine-server-blockchain/src/main/java/utils/search/UserSearchService.java @@ -1,70 +1,70 @@ -package utils.search; - - -import utils.blockchain.BchInfoManager; - -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -/** - * UserSearchService — поиск первых 5 пользователей по подстроке логина (без учёта регистра). - */ -public final class UserSearchService { - - private static final UserSearchService INSTANCE = new UserSearchService(); - private UserSearchService() {} - public static UserSearchService getInstance() { return INSTANCE; } - - /** Результат одной пары: id + исходный login (с родным регистром). */ - public static final class Pair { - public final long blockchainId; - public final String userLogin; - public Pair(long blockchainId, String userLogin) { - this.blockchainId = blockchainId; - this.userLogin = userLogin; - } - } - - /** - * Найти первые до 5 логинов, содержащих подстроку (case-insensitive). - */ - public List searchFirst5(String query) { - String q = (query == null ? "" : query).toLowerCase(Locale.ROOT).trim(); - List out = new ArrayList<>(5); - if (q.isEmpty()) return out; - - // берём снапшот id→login - Map all = BchInfoManager.getInstance().getAllLoginsSnapshot(); - - for (var e : all.entrySet()) { - if (out.size() >= 5) break; - String login = e.getValue() == null ? "" : e.getValue(); - if (login.toLowerCase(Locale.ROOT).contains(q)) { - out.add(new Pair(e.getKey(), login)); - } - } - return out; - } - - // Упаковка пары в байтовый формат ответа: [8] id + [1] L + [L] login UTF-8 (L<=255) - public static byte[] packPair(Pair p) { - byte[] loginUtf8 = (p.userLogin == null ? "" : p.userLogin).getBytes(StandardCharsets.UTF_8); - int L = Math.min(loginUtf8.length, 255); - byte[] b = new byte[8 + 1 + L]; - // beLong - b[0]=(byte)((p.blockchainId>>>56)&0xFF); - b[1]=(byte)((p.blockchainId>>>48)&0xFF); - b[2]=(byte)((p.blockchainId>>>40)&0xFF); - b[3]=(byte)((p.blockchainId>>>32)&0xFF); - b[4]=(byte)((p.blockchainId>>>24)&0xFF); - b[5]=(byte)((p.blockchainId>>>16)&0xFF); - b[6]=(byte)((p.blockchainId>>>8 )&0xFF); - b[7]=(byte)((p.blockchainId )&0xFF); - b[8]=(byte)L; - System.arraycopy(loginUtf8, 0, b, 9, L); - return b; - } -} +//package utils.search; +// +// +//import utils.blockchain.BchInfoManager; +// +//import java.nio.charset.StandardCharsets; +//import java.util.ArrayList; +//import java.util.List; +//import java.util.Locale; +//import java.util.Map; +// +///** +// * UserSearchService — поиск первых 5 пользователей по подстроке логина (без учёта регистра). +// */ +//public final class UserSearchService { +// +// private static final UserSearchService INSTANCE = new UserSearchService(); +// private UserSearchService() {} +// public static UserSearchService getInstance() { return INSTANCE; } +// +// /** Результат одной пары: id + исходный login (с родным регистром). */ +// public static final class Pair { +// public final long blockchainId; +// public final String userLogin; +// public Pair(long blockchainId, String userLogin) { +// this.blockchainId = blockchainId; +// this.userLogin = userLogin; +// } +// } +// +// /** +// * Найти первые до 5 логинов, содержащих подстроку (case-insensitive). +// */ +// public List searchFirst5(String query) { +// String q = (query == null ? "" : query).toLowerCase(Locale.ROOT).trim(); +// List out = new ArrayList<>(5); +// if (q.isEmpty()) return out; +// +// // берём снапшот id→login +// Map all = BchInfoManager.getInstance().getAllLoginsSnapshot(); +// +// for (var e : all.entrySet()) { +// if (out.size() >= 5) break; +// String login = e.getValue() == null ? "" : e.getValue(); +// if (login.toLowerCase(Locale.ROOT).contains(q)) { +// out.add(new Pair(e.getKey(), login)); +// } +// } +// return out; +// } +// +// // Упаковка пары в байтовый формат ответа: [8] id + [1] L + [L] login UTF-8 (L<=255) +// public static byte[] packPair(Pair p) { +// byte[] loginUtf8 = (p.userLogin == null ? "" : p.userLogin).getBytes(StandardCharsets.UTF_8); +// int L = Math.min(loginUtf8.length, 255); +// byte[] b = new byte[8 + 1 + L]; +// // beLong +// b[0]=(byte)((p.blockchainId>>>56)&0xFF); +// b[1]=(byte)((p.blockchainId>>>48)&0xFF); +// b[2]=(byte)((p.blockchainId>>>40)&0xFF); +// b[3]=(byte)((p.blockchainId>>>32)&0xFF); +// b[4]=(byte)((p.blockchainId>>>24)&0xFF); +// b[5]=(byte)((p.blockchainId>>>16)&0xFF); +// b[6]=(byte)((p.blockchainId>>>8 )&0xFF); +// b[7]=(byte)((p.blockchainId )&0xFF); +// b[8]=(byte)L; +// System.arraycopy(loginUtf8, 0, b, 9, L); +// return b; +// } +//}