Переписал код кучи классов перешёл на новый надеюсь теперь подходящий формат блоков и тесты переделал. Но пока остались баги и тесты не проходят (в частности пользователи не создаются - ошибка в бд)
130 lines
4.7 KiB
Java
130 lines
4.7 KiB
Java
// =======================
|
||
// blockchain/body/HeaderBody.java (ИЗМЕНЁННЫЙ: bodyBytes без type/subType/version)
|
||
// =======================
|
||
package blockchain.body;
|
||
|
||
import utils.config.ShineSignatureConstants;
|
||
|
||
import java.nio.ByteBuffer;
|
||
import java.nio.ByteOrder;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.util.Objects;
|
||
|
||
/**
|
||
* HeaderBody — type=0, version=1.
|
||
*
|
||
* В новом формате type/subType/version живут в HEADER блока,
|
||
* поэтому bodyBytes для HeaderBody содержат только payload:
|
||
*
|
||
* bodyBytes (BigEndian):
|
||
* [TAG_LEN] tag ASCII "SHiNE"
|
||
* [1] loginLength=N (uint8)
|
||
* [N] login UTF-8
|
||
*/
|
||
public final class HeaderBody implements BodyRecord {
|
||
|
||
public static final short TYPE = 0;
|
||
public static final short VER = 1;
|
||
|
||
public static final int KEY = ((TYPE & 0xFFFF) << 16) | (VER & 0xFFFF);
|
||
|
||
/** Для header subType всегда 0 (служебная совместимость). */
|
||
public static final short SUBTYPE_COMPAT = 0;
|
||
|
||
/** TAG формата (ASCII). */
|
||
public static final String TAG = ShineSignatureConstants.BLOCKCHAIN_HEADER_TAG;
|
||
|
||
private static final byte[] TAG_ASCII = TAG.getBytes(StandardCharsets.US_ASCII);
|
||
private static final int TAG_LEN = TAG_ASCII.length;
|
||
|
||
public final short subType; // всегда 0 (из заголовка блока)
|
||
public final short version; // из заголовка блока
|
||
public final String tag; // "SHiNE"
|
||
public final String login;
|
||
|
||
/** Десериализация из payload bodyBytes (без type/subType/version). */
|
||
public HeaderBody(short subType, short version, byte[] bodyBytes) {
|
||
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
|
||
|
||
this.subType = subType;
|
||
this.version = version;
|
||
|
||
if ((this.subType & 0xFFFF) != (SUBTYPE_COMPAT & 0xFFFF)) {
|
||
throw new IllegalArgumentException("HeaderBody subType must be 0, got=" + (this.subType & 0xFFFF));
|
||
}
|
||
if ((this.version & 0xFFFF) != (VER & 0xFFFF)) {
|
||
throw new IllegalArgumentException("HeaderBody version must be 1, got=" + (this.version & 0xFFFF));
|
||
}
|
||
|
||
// минимум: tag[TAG_LEN] + loginLen[1]
|
||
if (bodyBytes.length < TAG_LEN + 1) throw new IllegalArgumentException("HeaderBody too short");
|
||
|
||
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
||
|
||
byte[] tagBytes = new byte[TAG_LEN];
|
||
bb.get(tagBytes);
|
||
String t = new String(tagBytes, StandardCharsets.US_ASCII);
|
||
if (!TAG.equals(t)) throw new IllegalArgumentException("Bad tag: " + t);
|
||
this.tag = t;
|
||
|
||
int loginLen = Byte.toUnsignedInt(bb.get());
|
||
if (loginLen <= 0 || bb.remaining() < loginLen)
|
||
throw new IllegalArgumentException("Bad login length");
|
||
|
||
byte[] loginBytes = new byte[loginLen];
|
||
bb.get(loginBytes);
|
||
this.login = new String(loginBytes, StandardCharsets.UTF_8);
|
||
|
||
if (bb.remaining() != 0) throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
||
}
|
||
|
||
/** Создание “вручную”. */
|
||
public HeaderBody(String login) {
|
||
Objects.requireNonNull(login, "login == null");
|
||
this.subType = SUBTYPE_COMPAT;
|
||
this.version = VER;
|
||
this.tag = TAG;
|
||
this.login = login;
|
||
}
|
||
|
||
@Override
|
||
public HeaderBody check() {
|
||
if ((subType & 0xFFFF) != (SUBTYPE_COMPAT & 0xFFFF))
|
||
throw new IllegalArgumentException("HeaderBody subType must be 0");
|
||
|
||
if (login == null || login.isBlank())
|
||
throw new IllegalArgumentException("Login is blank");
|
||
if (!login.matches("^[A-Za-z0-9_]+$"))
|
||
throw new IllegalArgumentException("Login must match ^[A-Za-z0-9_]+$");
|
||
|
||
return this;
|
||
}
|
||
|
||
@Override
|
||
public byte[] toBytes() {
|
||
byte[] loginUtf8 = login.getBytes(StandardCharsets.UTF_8);
|
||
if (loginUtf8.length == 0 || loginUtf8.length > 255)
|
||
throw new IllegalArgumentException("Login utf8 len must be 1..255");
|
||
|
||
int cap = TAG_LEN + 1 + loginUtf8.length;
|
||
|
||
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
||
bb.put(TAG_ASCII);
|
||
bb.put((byte) loginUtf8.length);
|
||
bb.put(loginUtf8);
|
||
|
||
return bb.array();
|
||
}
|
||
|
||
@Override
|
||
public String toString() {
|
||
return """
|
||
HeaderBody {
|
||
тип записи : HEADER (type=0, ver=1) [в заголовке блока]
|
||
subType : 0 (compat)
|
||
тег формата : "%s"
|
||
login владельца : "%s"
|
||
}
|
||
""".formatted(tag, login);
|
||
}
|
||
} |