feat: finalize channels fixes and runtime stability

This commit is contained in:
DrygMira
2026-04-13 23:00:36 +03:00
parent 0c7d8fac02
commit a9c69e5947
46 changed files with 4654 additions and 356 deletions
@@ -68,6 +68,8 @@ public final class MsgSubType {
/** Лайк (LIKE). */
public static final short REACTION_LIKE = 1;
/** Снятие лайка (UNLIKE). */
public static final short REACTION_UNLIKE = 2;
/* ===================== CONNECTION (msg_type=3) ===================== */
@@ -90,4 +92,4 @@ public final class MsgSubType {
/** Параметр профиля key/value (обе строки). */
public static final short USER_PARAM_TEXT_TEXT = 1;
}
}
@@ -6,6 +6,7 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.regex.Pattern;
import java.util.Objects;
/**
@@ -39,6 +40,10 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
public static final short SUBTYPE = MsgSubType.TECH_CREATE_CHANNEL;
private static final byte[] ZERO32 = new byte[32];
private static final int MIN_NAME_LENGTH = 3;
private static final int MAX_NAME_LENGTH = 32;
private static final Pattern ALLOWED_NAME_PATTERN =
Pattern.compile("^[\\p{IsLatin}\\p{IsCyrillic}0-9 _-]+$");
public final short subType; // из header
public final short version; // из header
@@ -121,14 +126,15 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
if ((subType & 0xFFFF) != (SUBTYPE & 0xFFFF))
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1)");
if (channelName == null || channelName.isBlank())
String normalizedName = normalizeDisplayName(channelName);
if (normalizedName.isEmpty())
throw new IllegalArgumentException("channelName is blank");
if (!channelName.matches("^[A-Za-z0-9_]+$"))
throw new IllegalArgumentException("channelName must match ^[A-Za-z0-9_]+$");
if ("0".equals(channelName))
throw new IllegalArgumentException("channelName \"0\" is reserved");
int cpLen = normalizedName.codePointCount(0, normalizedName.length());
// Backward compatibility for historical blocks:
// strict create-channel rules are enforced in AddBlock handler (ChannelNameRules),
// but parser-level check must allow legacy channel names during bootstrap/replay.
if (cpLen > MAX_NAME_LENGTH)
throw new IllegalArgumentException("channelName length must be <=32");
// tech-line: prev обязателен (минимум HEADER=0)
if (prevLineNumber < 0)
@@ -141,6 +147,11 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
return this;
}
private static String normalizeDisplayName(String value) {
if (value == null) return "";
return value.trim().replaceAll("\\s+", " ");
}
@Override
public byte[] toBytes() {
byte[] nameUtf8 = channelName.getBytes(StandardCharsets.UTF_8);
@@ -167,4 +178,4 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
@Override public int prevLineBlockGlobalNumber() { return prevLineNumber; }
@Override public byte[] prevLineBlockHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
@Override public int lineSeq() { return thisLineNumber; }
}
}
@@ -13,6 +13,7 @@ import java.util.Objects;
*
* subType (в заголовке блока):
* 1 = LIKE
* 2 = UNLIKE
*
* bodyBytes (BigEndian), новый формат:
* [1] toBlockchainNameLen (uint8)
@@ -45,7 +46,7 @@ public final class ReactionBody implements BodyRecord, BodyHasTarget {
if ((this.version & 0xFFFF) != (VER & 0xFFFF)) {
throw new IllegalArgumentException("ReactionBody version must be 1, got=" + (this.version & 0xFFFF));
}
if ((this.subType & 0xFFFF) != (MsgSubType.REACTION_LIKE & 0xFFFF)) {
if (!isSupportedSubType(this.subType)) {
throw new IllegalArgumentException("Bad reaction subType: " + (this.subType & 0xFFFF));
}
@@ -88,7 +89,7 @@ public final class ReactionBody implements BodyRecord, BodyHasTarget {
@Override
public ReactionBody check() {
if ((subType & 0xFFFF) != (MsgSubType.REACTION_LIKE & 0xFFFF))
if (!isSupportedSubType(subType))
throw new IllegalArgumentException("Bad reaction subType: " + (subType & 0xFFFF));
if (toBlockchainName == null || toBlockchainName.isBlank())
@@ -123,4 +124,10 @@ public final class ReactionBody implements BodyRecord, BodyHasTarget {
@Override public String toBchName() { return toBlockchainName; }
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
@Override public byte[] toBlockHashBytes() { return toBlockHash32; }
}
private static boolean isSupportedSubType(short subType) {
int st = subType & 0xFFFF;
return st == (MsgSubType.REACTION_LIKE & 0xFFFF)
|| st == (MsgSubType.REACTION_UNLIKE & 0xFFFF);
}
}