SHA256
feat: finalize channels fixes and runtime stability
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user