Потч работает добавление линий - ситуация сложная

тест падает
This commit is contained in:
AidarKC
2026-01-21 18:37:05 +03:00
parent 376d42cd79
commit 69cd33479b
13 changed files with 396 additions and 149 deletions
@@ -15,7 +15,7 @@ import java.util.Objects;
* RAW (BigEndian) = preimage:
* [32] prevHash32 (SHA-256) hash предыдущего блока (цепочка)
* [4] blockSize (int) = размер preimage (в байтах), БЕЗ signature64
* [4] blockNumber (int) глобальный номер блока
* [4] blockNumber (int) глобальный номер блока (>=0)
* [8] timestamp (long) unix seconds
*
* [2] type (short) тип сообщения
@@ -62,7 +62,7 @@ public final class BchBlockEntry {
// --- HEADER (RAW) ---
public final byte[] prevHash32; // 32
public final int blockSize; // preimage size
public final int blockNumber;
public final int blockNumber; // >=0
public final long timestamp;
public final short type;
public final short subType;
@@ -113,6 +113,10 @@ public final class BchBlockEntry {
}
this.blockNumber = bb.getInt();
if (this.blockNumber < 0) {
throw new IllegalArgumentException("blockNumber < 0: " + this.blockNumber);
}
this.timestamp = bb.getLong();
// запрет “в будущее” больше чем на 1 минуту
@@ -171,6 +175,10 @@ public final class BchBlockEntry {
if (prevHash32.length != 32) throw new IllegalArgumentException("prevHash32 != 32");
if (signature64.length != SIGNATURE_LEN) throw new IllegalArgumentException("signature64 != 64");
if (blockNumber < 0) {
throw new IllegalArgumentException("blockNumber < 0: " + blockNumber);
}
// запрет “в будущее” больше чем на 1 минуту
long now = Instant.now().getEpochSecond();
if (timestamp > now + MAX_FUTURE_SECONDS) {
@@ -3,13 +3,10 @@ package blockchain.body;
/**
* BodyHasLine — для типов, которые имеют линейные поля в body.
*
* В проекте hasLine встречается, например, у:
* - TECH: CREATE_CHANNEL (type=0, subType=1) — идёт по тех-линии
* - TEXT: POST / EDIT_POST (type=1, subType=10/11) — линия канала
* - CONNECTION (type=3)
* - USER_PARAM (type=4)
*
* Формат линейных полей (BigEndian) в НАЧАЛЕ bodyBytes:
* Новый префикс для line-сообщений (BigEndian) в НАЧАЛЕ bodyBytes:
* [4] lineCode код линии:
* - 0 для нулевой линии
* - для каналов: blockNumber "заглавия линии" (CREATE_CHANNEL или HEADER/0)
* [4] prevLineNumber
* [32] prevLineHash32
* [4] thisLineNumber
@@ -20,6 +17,8 @@ package blockchain.body;
*/
public interface BodyHasLine {
int lineCode();
int prevLineNumber();
byte[] prevLineHash32();
@@ -18,6 +18,7 @@ import java.util.Objects;
* FOLLOW=30, UNFOLLOW=31
*
* bodyBytes (BigEndian), новый формат (toLogin НЕ ХРАНИМ):
* [4] lineCode
* [4] prevLineNumber
* [32] prevLineHash32
* [4] thisLineNumber
@@ -41,6 +42,7 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
public final short version; // из header
// line
public final int lineCode;
public final int prevLineNumber;
public final byte[] prevLineHash32;
public final int thisLineNumber;
@@ -64,13 +66,15 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
}
// минимум:
// line(4+32+4) + toBchLen[1]+toBch[1] + global[4] + hash[32]
if (bodyBytes.length < (4 + 32 + 4) + 1 + 1 + 4 + 32) {
// lineCode(4) + line(4+32+4) + toBchLen[1]+toBch[1] + global[4] + hash[32]
if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1 + 4 + 32) {
throw new IllegalArgumentException("ConnectionBody too short");
}
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
this.lineCode = bb.getInt();
this.prevLineNumber = bb.getInt();
this.prevLineHash32 = new byte[32];
@@ -94,7 +98,8 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
}
public ConnectionBody(int prevLineNumber,
public ConnectionBody(int lineCode,
int prevLineNumber,
byte[] prevLineHash32,
int thisLineNumber,
short subType,
@@ -105,6 +110,7 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
Objects.requireNonNull(toBlockchainName, "toBlockchainName == null");
Objects.requireNonNull(toBlockHash32, "toBlockHash32 == null");
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
if (!isValidSubType(subType)) throw new IllegalArgumentException("Bad connection subType: " + (subType & 0xFFFF));
if (toBlockchainName.isBlank()) throw new IllegalArgumentException("toBlockchainName is blank");
@@ -116,6 +122,8 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
if (toBlockGlobalNumber < 0) throw new IllegalArgumentException("toBlockGlobalNumber < 0");
if (toBlockHash32.length != 32) throw new IllegalArgumentException("toBlockHash32 != 32");
this.lineCode = lineCode;
this.prevLineNumber = prevLineNumber;
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
this.thisLineNumber = thisLineNumber;
@@ -140,9 +148,10 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
@Override
public ConnectionBody check() {
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
if (!isValidSubType(subType)) throw new IllegalArgumentException("Bad connection subType: " + (subType & 0xFFFF));
// line rule
// line rule (как было)
if (prevLineNumber == -1) {
if (!isAllZero32(prevLineHash32)) throw new IllegalArgumentException("prevLineHash32 must be zero when prevLineNumber=-1");
if (thisLineNumber != -1) throw new IllegalArgumentException("thisLineNumber must be -1 when prevLineNumber=-1");
@@ -172,12 +181,14 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
if (toBlockHash32 == null || toBlockHash32.length != 32)
throw new IllegalArgumentException("toBlockHash32 != 32");
int cap = (4 + 32 + 4)
int cap = 4 + (4 + 32 + 4)
+ 1 + bchBytes.length
+ 4 + 32;
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
bb.putInt(lineCode);
bb.putInt(prevLineNumber);
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
bb.putInt(thisLineNumber);
@@ -198,12 +209,12 @@ public final class ConnectionBody implements BodyRecord, BodyHasTarget, BodyHasL
}
/* ====================== BodyHasLine ====================== */
@Override public int lineCode() { return lineCode; }
@Override public int prevLineNumber() { return prevLineNumber; }
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
@Override public int thisLineNumber() { return thisLineNumber; }
/* ====================== BodyHasTarget ===================== */
// toLogin() теперь default в интерфейсе и вычисляется из toBchName()
@Override public String toBchName() { return toBlockchainName; }
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
@Override public byte[] toBlockHashBytes() { return toBlockHash32; }
@@ -18,7 +18,8 @@ import java.util.Objects;
* - prevLineNumber/hash указывают на предыдущее TECH-сообщение (HEADER или прошлый CREATE_CHANNEL)
* - thisLineNumber: 1,2,3... (тех-нумерация)
*
* bodyBytes (BigEndian):
* bodyBytes (BigEndian), новый формат line-prefix:
* [4] lineCode (для TECH линии обычно 0)
* [4] prevLineNumber
* [32] prevLineHash32
* [4] thisLineNumber
@@ -43,6 +44,7 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
public final short version; // из header
// line
public final int lineCode;
public final int prevLineNumber;
public final byte[] prevLineHash32; // 32
public final int thisLineNumber;
@@ -63,12 +65,15 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1), got=" + (this.subType & 0xFFFF));
}
if (bodyBytes.length < (4 + 32 + 4) + 1 + 1) {
// минимум: lineCode(4) + line(4+32+4) + nameLen(1) + name(1)
if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1) {
throw new IllegalArgumentException("CreateChannelBody too short");
}
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
this.lineCode = bb.getInt();
this.prevLineNumber = bb.getInt();
this.prevLineHash32 = new byte[32];
@@ -90,12 +95,18 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
}
public CreateChannelBody(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber, String channelName) {
public CreateChannelBody(int lineCode,
int prevLineNumber,
byte[] prevLineHash32,
int thisLineNumber,
String channelName) {
Objects.requireNonNull(channelName, "channelName == null");
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
this.subType = SUBTYPE;
this.version = VER;
this.lineCode = lineCode;
this.prevLineNumber = prevLineNumber;
this.prevLineHash32 = (prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32));
this.thisLineNumber = thisLineNumber;
@@ -105,6 +116,8 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
@Override
public CreateChannelBody check() {
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
if ((subType & 0xFFFF) != (SUBTYPE & 0xFFFF))
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1)");
@@ -134,9 +147,11 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
if (nameUtf8.length == 0 || nameUtf8.length > 255)
throw new IllegalArgumentException("channelName utf8 len must be 1..255");
int cap = (4 + 32 + 4) + 1 + nameUtf8.length;
int cap = 4 + (4 + 32 + 4) + 1 + nameUtf8.length;
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
bb.putInt(lineCode);
bb.putInt(prevLineNumber);
bb.put(prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32));
bb.putInt(thisLineNumber);
@@ -148,6 +163,7 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
}
/* ====================== BodyHasLine ====================== */
@Override public int lineCode() { return lineCode; }
@Override public int prevLineNumber() { return prevLineNumber; }
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
@Override public int thisLineNumber() { return thisLineNumber; }
@@ -22,32 +22,29 @@ import java.util.Objects;
* =========================================================================
* КОНЦЕПЦИЯ ЛИНИЙ ДЛЯ ТЕКСТОВЫХ СООБЩЕНИЙ:
*
* POST и EDIT_POST принадлежат ЛИНИИ КАНАЛА и имеют hasLine:
* [4] prevLineNumber
* [32] prevLineHash32
* [4] thisLineNumber
* POST и EDIT_POST принадлежат ЛИНИИ КАНАЛА и имеют hasLine.
* В новом формате добавлен lineCode:
* lineCode = 0 для канала "0"
* lineCode = blockNumber "заглавия линии/канала" (например CREATE_CHANNEL)
*
* Канал в POST/EDIT_POST НЕ хранится (channelName не лежит в bodyBytes).
* Канал определяется логически через lineRootBlockNumber:
* - канал "0": lineRootBlockNumber = blockNumber заголовка (HEADER)
* - канал "X": lineRootBlockNumber = blockNumber тех-сообщения CREATE_CHANNEL("X")
*
* REPLY и EDIT_REPLY НЕ имеют линии (нет hasLine).
* REPLY и EDIT_REPLY НЕ имеют линии (нет hasLine в байтах).
*
* =========================================================================
* ФОРМАТЫ bodyBytes (BigEndian):
*
* 1) POST (subType=10):
* [4] lineCode
* [4] prevLineNumber
* [32] prevLineHash32
* [4] thisLineNumber // 0,1,2...
* [4] thisLineNumber
* [2] textLenBytes (uint16)
* [N] text UTF-8
*
* 2) EDIT_POST (subType=11):
* [4] lineCode
* [4] prevLineNumber
* [32] prevLineHash32
* [4] thisLineNumber // равен thisLineNumber предыдущего сообщения линии
* [4] thisLineNumber
*
* hasTarget (на ОРИГИНАЛЬНЫЙ POST, toBchName НЕ хранить):
* [4] toBlockGlobalNumber
@@ -57,7 +54,7 @@ import java.util.Objects;
* [N] text UTF-8
*
* 3) REPLY (subType=20) — НЕ в линии:
* hasTarget (может быть на чужой блокчейн; существование НЕ проверяем):
* hasTarget:
* [1] toBlockchainNameLen (uint8)
* [N] toBlockchainName UTF-8
* [4] toBlockGlobalNumber
@@ -73,15 +70,6 @@ import java.util.Objects;
*
* [2] textLenBytes (uint16)
* [N] text UTF-8
*
* =========================================================================
* ВАЖНО:
* - Body.check() НЕ имеет доступа к БД, поэтому:
* - не проверяет существование prevLineNumber/hash
* - не проверяет согласование thisLineNumber относительно prev
* - не проверяет существование target для REPLY
*
* Эти проверки выполняются на сервере/в БД при вставке.
*/
public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
@@ -95,6 +83,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
// ===== line fields (только для POST/EDIT_POST) =====
// Для REPLY/EDIT_REPLY эти поля НЕ сериализуются; значения держим как "пустые".
public final int lineCode; // только для line-message; иначе -1
public final int prevLineNumber;
public final byte[] prevLineHash32; // 32 or null
public final int thisLineNumber;
@@ -105,9 +94,9 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
// ===== target fields =====
// REPLY: toBlockchainName + globalNumber + hash32
// EDIT_POST / EDIT_REPLY: только globalNumber + hash32 (без toBlockchainName)
public final String toBlockchainName; // nullable
public final String toBlockchainName; // nullable
public final Integer toBlockGlobalNumber; // nullable
public final byte[] toBlockHash32; // nullable(но если target есть -> 32)
public final byte[] toBlockHash32; // nullable (но если target есть -> 32)
/* ===================================================================== */
/* ====================== Конструктор из байт ========================== */
@@ -131,9 +120,10 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
int st = this.subType & 0xFFFF;
if (st == (MsgSubType.TEXT_POST & 0xFFFF)) {
// POST: hasLine + text
ensureMin(bb, (4 + 32 + 4) + 2, "POST too short");
// POST: hasLine(lineCode+line) + text
ensureMin(bb, (4 + 4 + 32 + 4) + 2, "POST too short");
this.lineCode = bb.getInt();
this.prevLineNumber = bb.getInt();
this.prevLineHash32 = new byte[32];
bb.get(this.prevLineHash32);
@@ -148,9 +138,10 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
ensureNoTail(bb, "POST");
} else if (st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
// EDIT_POST: hasLine + target(no bch) + text
ensureMin(bb, (4 + 32 + 4) + (4 + 32) + 2, "EDIT_POST too short");
// EDIT_POST: hasLine(lineCode+line) + target(no bch) + text
ensureMin(bb, (4 + 4 + 32 + 4) + (4 + 32) + 2, "EDIT_POST too short");
this.lineCode = bb.getInt();
this.prevLineNumber = bb.getInt();
this.prevLineHash32 = new byte[32];
bb.get(this.prevLineHash32);
@@ -169,7 +160,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
ensureNoTail(bb, "EDIT_POST");
} else if (st == (MsgSubType.TEXT_REPLY & 0xFFFF)) {
// REPLY: target(with bch) + text
// REPLY: target(with bch) + text (без line)
ensureMin(bb, 1 + 1 + 4 + 32 + 2, "REPLY too short");
int nameLen = Byte.toUnsignedInt(bb.get());
@@ -188,6 +179,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
this.message = readStrictUtf8Len16(bb, "REPLY text");
// line fields отсутствуют в байтах
this.lineCode = -1;
this.prevLineNumber = -1;
this.prevLineHash32 = null;
this.thisLineNumber = -1;
@@ -195,7 +187,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
ensureNoTail(bb, "REPLY");
} else if (st == (MsgSubType.TEXT_EDIT_REPLY & 0xFFFF)) {
// EDIT_REPLY: target(no bch) + text
// EDIT_REPLY: target(no bch) + text (без line)
ensureMin(bb, (4 + 32) + 2, "EDIT_REPLY too short");
int tgtNum = bb.getInt();
@@ -209,6 +201,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
this.message = readStrictUtf8Len16(bb, "EDIT_REPLY text");
// line fields отсутствуют в байтах
this.lineCode = -1;
this.prevLineNumber = -1;
this.prevLineHash32 = null;
this.thisLineNumber = -1;
@@ -216,7 +209,6 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
ensureNoTail(bb, "EDIT_REPLY");
} else {
// недостижимо из-за isValidSubType, но пусть будет
throw new IllegalArgumentException("Unsupported Text subType: " + st);
}
}
@@ -225,25 +217,25 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
/* ====================== Фабрики (удобно) ============================= */
/* ===================================================================== */
public static TextBody newPost(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber, String message) {
return new TextBody(MsgSubType.TEXT_POST, prevLineNumber, prevLineHash32, thisLineNumber,
public static TextBody newPost(int lineCode, int prevLineNumber, byte[] prevLineHash32, int thisLineNumber, String message) {
return new TextBody(MsgSubType.TEXT_POST, lineCode, prevLineNumber, prevLineHash32, thisLineNumber,
message, null, null, null);
}
public static TextBody newEditPost(int prevLineNumber, byte[] prevLineHash32, int thisLineNumber,
public static TextBody newEditPost(int lineCode, int prevLineNumber, byte[] prevLineHash32, int thisLineNumber,
int targetBlockNumber, byte[] targetHash32,
String message) {
return new TextBody(MsgSubType.TEXT_EDIT_POST, prevLineNumber, prevLineHash32, thisLineNumber,
return new TextBody(MsgSubType.TEXT_EDIT_POST, lineCode, prevLineNumber, prevLineHash32, thisLineNumber,
message, null, targetBlockNumber, targetHash32);
}
public static TextBody newReply(String toBlockchainName, int targetBlockNumber, byte[] targetHash32, String message) {
return new TextBody(MsgSubType.TEXT_REPLY, -1, null, -1,
return new TextBody(MsgSubType.TEXT_REPLY, -1, -1, null, -1,
message, toBlockchainName, targetBlockNumber, targetHash32);
}
public static TextBody newEditReply(int targetBlockNumber, byte[] targetHash32, String message) {
return new TextBody(MsgSubType.TEXT_EDIT_REPLY, -1, null, -1,
return new TextBody(MsgSubType.TEXT_EDIT_REPLY, -1, -1, null, -1,
message, null, targetBlockNumber, targetHash32);
}
@@ -252,6 +244,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
* Для REPLY/EDIT_REPLY line поля игнорируются при сериализации (их в формате нет).
*/
public TextBody(short subType,
int lineCode,
int prevLineNumber,
byte[] prevLineHash32,
int thisLineNumber,
@@ -272,10 +265,13 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
// line применима только к POST/EDIT_POST
if (st == (MsgSubType.TEXT_POST & 0xFFFF) || st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0 for line message");
this.lineCode = lineCode;
this.prevLineNumber = prevLineNumber;
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
this.thisLineNumber = thisLineNumber;
} else {
this.lineCode = -1;
this.prevLineNumber = -1;
this.prevLineHash32 = null;
this.thisLineNumber = -1;
@@ -322,7 +318,6 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
this.toBlockHash32 = Arrays.copyOf(toBlockHash32, 32);
} else {
// недостижимо
this.toBlockchainName = null;
this.toBlockGlobalNumber = null;
this.toBlockHash32 = null;
@@ -349,6 +344,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
// локальные проверки line (БД не трогаем)
if (st == (MsgSubType.TEXT_POST & 0xFFFF) || st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0 for line message");
if (prevLineHash32 == null || prevLineHash32.length != 32)
throw new IllegalArgumentException("prevLineHash32 invalid");
} else {
@@ -399,10 +395,11 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
int st = subType & 0xFFFF;
if (st == (MsgSubType.TEXT_POST & 0xFFFF)) {
// hasLine + text
int cap = (4 + 32 + 4) + 2 + msgUtf8.length;
// hasLine(lineCode+line) + text
int cap = (4 + 4 + 32 + 4) + 2 + msgUtf8.length;
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
bb.putInt(lineCode);
bb.putInt(prevLineNumber);
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
bb.putInt(thisLineNumber);
@@ -411,13 +408,14 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
return bb.array();
} else if (st == (MsgSubType.TEXT_EDIT_POST & 0xFFFF)) {
// hasLine + target(no bch) + text
// hasLine(lineCode+line) + target(no bch) + text
if (toBlockGlobalNumber == null) throw new IllegalArgumentException("EDIT_POST missing toBlockGlobalNumber");
if (toBlockHash32 == null || toBlockHash32.length != 32) throw new IllegalArgumentException("EDIT_POST toBlockHash32 != 32");
int cap = (4 + 32 + 4) + (4 + 32) + 2 + msgUtf8.length;
int cap = (4 + 4 + 32 + 4) + (4 + 32) + 2 + msgUtf8.length;
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
bb.putInt(lineCode);
bb.putInt(prevLineNumber);
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
bb.putInt(thisLineNumber);
@@ -506,6 +504,7 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
}
/* ====================== BodyHasLine ====================== */
@Override public int lineCode() { return lineCode; }
@Override public int prevLineNumber() { return prevLineNumber; }
@Override public byte[] prevLineHash32() {
if (prevLineHash32 == null) return null;
@@ -518,8 +517,6 @@ public final class TextBody implements BodyRecord, BodyHasTarget, BodyHasLine {
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
@Override public byte[] toBlockHashBytes() { return toBlockHash32; }
/* ===================================================================== */
/* ===================== Удобные хелперы (для ChainState) =============== */
/* ===================================================================== */
@@ -17,6 +17,7 @@ import java.util.Objects;
* 1 = TEXT_TEXT
*
* bodyBytes (BigEndian), новый формат:
* [4] lineCode
* [4] prevLineNumber
* [32] prevLineHash32
* [4] thisLineNumber
@@ -38,6 +39,7 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
public final short version; // из header
// line
public final int lineCode;
public final int prevLineNumber;
public final byte[] prevLineHash32;
public final int thisLineNumber;
@@ -58,13 +60,15 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
throw new IllegalArgumentException("Bad UserParam subType: " + (this.subType & 0xFFFF));
}
// минимум: line(4+32+4) + keyLen(2)+key(1) + valLen(2)+val(1)
if (bodyBytes.length < (4 + 32 + 4) + 2 + 1 + 2 + 1) {
// минимум: lineCode(4)+line(4+32+4) + keyLen(2)+key(1) + valLen(2)+val(1)
if (bodyBytes.length < 4 + (4 + 32 + 4) + 2 + 1 + 2 + 1) {
throw new IllegalArgumentException("UserParamBody too short");
}
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
this.lineCode = bb.getInt();
this.prevLineNumber = bb.getInt();
this.prevLineHash32 = new byte[32];
@@ -95,7 +99,8 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
if (this.paramValue.isBlank()) throw new IllegalArgumentException("paramValue is blank");
}
public UserParamBody(int prevLineNumber,
public UserParamBody(int lineCode,
int prevLineNumber,
byte[] prevLineHash32,
int thisLineNumber,
String paramKey,
@@ -104,9 +109,12 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
Objects.requireNonNull(paramKey, "paramKey == null");
Objects.requireNonNull(paramValue, "paramValue == null");
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
this.subType = MsgSubType.USER_PARAM_TEXT_TEXT;
this.version = VER;
this.lineCode = lineCode;
this.prevLineNumber = prevLineNumber;
this.prevLineHash32 = (prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
this.thisLineNumber = thisLineNumber;
@@ -120,6 +128,8 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
@Override
public UserParamBody check() {
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
if ((subType & 0xFFFF) != (MsgSubType.USER_PARAM_TEXT_TEXT & 0xFFFF))
throw new IllegalArgumentException("Bad UserParam subType: " + (subType & 0xFFFF));
@@ -144,12 +154,14 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
if (keyUtf8.length == 0 || keyUtf8.length > 65535) throw new IllegalArgumentException("paramKey utf8 len must be 1..65535");
if (valUtf8.length == 0 || valUtf8.length > 65535) throw new IllegalArgumentException("paramValue utf8 len must be 1..65535");
int cap = (4 + 32 + 4)
int cap = 4 + (4 + 32 + 4)
+ 2 + keyUtf8.length
+ 2 + valUtf8.length;
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
bb.putInt(lineCode);
bb.putInt(prevLineNumber);
bb.put(prevLineHash32 == null ? new byte[32] : Arrays.copyOf(prevLineHash32, 32));
bb.putInt(thisLineNumber);
@@ -182,6 +194,7 @@ public final class UserParamBody implements BodyRecord, BodyHasLine {
}
/* ====================== BodyHasLine ====================== */
@Override public int lineCode() { return lineCode; }
@Override public int prevLineNumber() { return prevLineNumber; }
@Override public byte[] prevLineHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
@Override public int thisLineNumber() { return thisLineNumber; }