SHA256
28 12 25
Вроде как сделал работу с линиями :) но ещё не тестил
This commit is contained in:
@@ -4,7 +4,7 @@ package blockchain.body;
|
||||
* BodyRecord_new — общий контракт для всех типов body (тела блока).
|
||||
*
|
||||
* Идея:
|
||||
* - На каждый тип body (Header, Text, File, ...) — отдельный класс.
|
||||
* - На каждый тип body (Header, Text, Reaction, ...) — отдельный класс.
|
||||
* - Десериализация из байтов делается КОНСТРУКТОРОМ:
|
||||
* new XxxBody_new(byte[] bodyBytes)
|
||||
* (конструктор обязан распарсить байты или кинуть IllegalArgumentException).
|
||||
@@ -18,21 +18,28 @@ package blockchain.body;
|
||||
*
|
||||
* - type() и version() — это идентификаторы формата body.
|
||||
* Они должны быть константами для класса (например TYPE=1, VERSION=1).
|
||||
*
|
||||
* ДОПОЛНЕНИЕ (ЛИНИИ):
|
||||
* - Каждый тип body знает, в какой lineIndex он ДОЛЖЕН находиться.
|
||||
* Это проверяется в валидаторе блока (уровень B).
|
||||
*/
|
||||
public interface BodyRecord {
|
||||
|
||||
/** Код типа записи (совпадает с recordType в BchBlockEntry). */
|
||||
/** Код типа записи (совпадает с type в bodyBytes). */
|
||||
short type();
|
||||
|
||||
/** Версия формата записи (совпадает с recordTypeVersion в BchBlockEntry). */
|
||||
/** Версия формата записи (совпадает с version в bodyBytes). */
|
||||
short version();
|
||||
|
||||
/** Ожидаемый индекс линии для этого body. */
|
||||
short expectedLineIndex();
|
||||
|
||||
/** Проверить корректность содержимого и вернуть этот объект (или кинуть исключение). */
|
||||
BodyRecord check();
|
||||
|
||||
/**
|
||||
* Сериализовать тело записи в байты (ровно то, что кладётся в block.body).
|
||||
* Важно: НЕ включает общий заголовок блока (recordNumber/timestamp/type/version).
|
||||
* Важно: включает type/version.
|
||||
*/
|
||||
byte[] toBytes();
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,9 @@ public final class BodyRecordParser {
|
||||
int key = ((type & 0xFFFF) << 16) | (ver & 0xFFFF);
|
||||
|
||||
return switch (key) {
|
||||
case 0x0000_0001 -> new HeaderBody(bodyBytes); // type=0, ver=1
|
||||
case 0x0001_0001 -> new TextBody(bodyBytes); // type=1, ver=1
|
||||
case 0x0000_0001 -> new HeaderBody(bodyBytes); // type=0, ver=1
|
||||
case 0x0001_0001 -> new TextBody(bodyBytes); // type=1, ver=1
|
||||
case 0x0002_0001 -> new ReactionBody(bodyBytes); // type=2, ver=1
|
||||
default -> throw new IllegalArgumentException(String.format(
|
||||
"Unknown body type/version: type=%d ver=%d (key=0x%08X)",
|
||||
(type & 0xFFFF), (ver & 0xFFFF), key
|
||||
|
||||
@@ -14,6 +14,9 @@ import java.util.Objects;
|
||||
* [8] tag ASCII "SHiNE001"
|
||||
* [1] loginLength=N (uint8)
|
||||
* [N] login UTF-8
|
||||
*
|
||||
* ЛИНИЯ:
|
||||
* - строго lineIndex=0 (genesis)
|
||||
*/
|
||||
public final class HeaderBody implements BodyRecord {
|
||||
|
||||
@@ -64,6 +67,11 @@ public final class HeaderBody implements BodyRecord {
|
||||
@Override public short type() { return TYPE; }
|
||||
@Override public short version() { return VER; }
|
||||
|
||||
@Override
|
||||
public short expectedLineIndex() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HeaderBody check() {
|
||||
if (login == null || login.isBlank())
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package blockchain.body;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ReactionBody — type=2, version=1.
|
||||
*
|
||||
* Сериализация bodyBytes:
|
||||
* [2] type=2
|
||||
* [2] ver=1
|
||||
* [4] reactionCode (int32)
|
||||
* [1] toBlockchainNameLen (uint8)
|
||||
* [N] toBlockchainName UTF-8
|
||||
* [4] toBlockGlobalNumber (int32)
|
||||
* [32] toBlockHash (raw 32 bytes)
|
||||
*
|
||||
* ЛИНИЯ:
|
||||
* - строго lineIndex=2
|
||||
*
|
||||
* ВАЖНО:
|
||||
* - Здесь мы НЕ проверяем, существует ли цель реакции (MVP правило).
|
||||
*/
|
||||
public final class ReactionBody implements BodyRecord {
|
||||
|
||||
public static final short TYPE = 2;
|
||||
public static final short VER = 1;
|
||||
|
||||
public final int reactionCode;
|
||||
public final String toBlockchainName;
|
||||
public final int toBlockGlobalNumber;
|
||||
public final byte[] toBlockHash32;
|
||||
|
||||
/** Десериализация из полного bodyBytes (включая type/version). */
|
||||
public ReactionBody(byte[] bodyBytes) {
|
||||
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
|
||||
if (bodyBytes.length < 4 + 4 + 1 + 1 + 4 + 32) {
|
||||
throw new IllegalArgumentException("ReactionBody too short");
|
||||
}
|
||||
|
||||
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
||||
|
||||
short type = bb.getShort();
|
||||
short ver = bb.getShort();
|
||||
if (type != TYPE || ver != VER)
|
||||
throw new IllegalArgumentException("Not ReactionBody: type=" + type + " ver=" + ver);
|
||||
|
||||
this.reactionCode = bb.getInt();
|
||||
|
||||
int nameLen = Byte.toUnsignedInt(bb.get());
|
||||
if (nameLen <= 0) throw new IllegalArgumentException("toBlockchainNameLen is 0");
|
||||
if (bb.remaining() < nameLen + 4 + 32) throw new IllegalArgumentException("ReactionBody payload too short");
|
||||
|
||||
byte[] nameBytes = new byte[nameLen];
|
||||
bb.get(nameBytes);
|
||||
this.toBlockchainName = new String(nameBytes, StandardCharsets.UTF_8);
|
||||
|
||||
this.toBlockGlobalNumber = bb.getInt();
|
||||
|
||||
this.toBlockHash32 = new byte[32];
|
||||
bb.get(this.toBlockHash32);
|
||||
}
|
||||
|
||||
public ReactionBody(int reactionCode, String toBlockchainName, int toBlockGlobalNumber, byte[] toBlockHash32) {
|
||||
Objects.requireNonNull(toBlockchainName, "toBlockchainName == null");
|
||||
Objects.requireNonNull(toBlockHash32, "toBlockHash32 == null");
|
||||
if (toBlockchainName.isBlank()) throw new IllegalArgumentException("toBlockchainName is blank");
|
||||
if (toBlockHash32.length != 32) throw new IllegalArgumentException("toBlockHash32 != 32");
|
||||
|
||||
this.reactionCode = reactionCode;
|
||||
this.toBlockchainName = toBlockchainName;
|
||||
this.toBlockGlobalNumber = toBlockGlobalNumber;
|
||||
this.toBlockHash32 = Arrays.copyOf(toBlockHash32, 32);
|
||||
}
|
||||
|
||||
@Override public short type() { return TYPE; }
|
||||
@Override public short version() { return VER; }
|
||||
|
||||
@Override
|
||||
public short expectedLineIndex() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReactionBody check() {
|
||||
if (toBlockchainName == null || toBlockchainName.isBlank())
|
||||
throw new IllegalArgumentException("toBlockchainName is blank");
|
||||
byte[] nameBytes = toBlockchainName.getBytes(StandardCharsets.UTF_8);
|
||||
if (nameBytes.length == 0 || nameBytes.length > 255)
|
||||
throw new IllegalArgumentException("toBlockchainName utf8 len must be 1..255");
|
||||
|
||||
if (toBlockGlobalNumber < 0)
|
||||
throw new IllegalArgumentException("toBlockGlobalNumber < 0");
|
||||
|
||||
if (toBlockHash32 == null || toBlockHash32.length != 32)
|
||||
throw new IllegalArgumentException("toBlockHash32 invalid");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
byte[] nameBytes = toBlockchainName.getBytes(StandardCharsets.UTF_8);
|
||||
if (nameBytes.length == 0 || nameBytes.length > 255)
|
||||
throw new IllegalArgumentException("toBlockchainName utf8 len must be 1..255");
|
||||
|
||||
int cap = 4 + 4 + 1 + nameBytes.length + 4 + 32;
|
||||
|
||||
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
||||
bb.putShort(TYPE);
|
||||
bb.putShort(VER);
|
||||
bb.putInt(reactionCode);
|
||||
bb.put((byte) nameBytes.length);
|
||||
bb.put(nameBytes);
|
||||
bb.putInt(toBlockGlobalNumber);
|
||||
bb.put(toBlockHash32);
|
||||
|
||||
return bb.array();
|
||||
}
|
||||
|
||||
/** Для записи в БД (toBlockHashe TEXT) удобно хранить hex. */
|
||||
public String toBlockHashHex() {
|
||||
return toHex(toBlockHash32);
|
||||
}
|
||||
|
||||
private static String toHex(byte[] bytes) {
|
||||
char[] HEX = "0123456789abcdef".toCharArray();
|
||||
char[] out = new char[bytes.length * 2];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
int v = bytes[i] & 0xFF;
|
||||
out[i * 2] = HEX[v >>> 4];
|
||||
out[i * 2 + 1] = HEX[v & 0x0F];
|
||||
}
|
||||
return new String(out);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,17 @@ import java.nio.charset.CodingErrorAction;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* TextBody — type=1, ver=1.
|
||||
*
|
||||
* bodyBytes:
|
||||
* [2] type=1
|
||||
* [2] ver=1
|
||||
* [N] utf8 message
|
||||
*
|
||||
* ЛИНИЯ:
|
||||
* - строго lineIndex=1
|
||||
*/
|
||||
public final class TextBody implements BodyRecord {
|
||||
|
||||
public static final short TYPE = 1;
|
||||
@@ -53,6 +64,11 @@ public final class TextBody implements BodyRecord {
|
||||
@Override public short type() { return TYPE; }
|
||||
@Override public short version() { return VER; }
|
||||
|
||||
@Override
|
||||
public short expectedLineIndex() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TextBody check() {
|
||||
if (message == null || message.isBlank())
|
||||
|
||||
Reference in New Issue
Block a user