SHA256
Добавил боди для связей Дальше делать: Описание форматов. Запросы клиент-сервер. Промт на клиента. --- Потом в сервак дописать Синхронизацию серверов.
146 lines
5.0 KiB
Java
146 lines
5.0 KiB
Java
package blockchain.body;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Objects;
|
|
|
|
/**
|
|
* HeaderBody — type=0, version=1.
|
|
*
|
|
* Полный bodyBytes (BigEndian):
|
|
* [2] type=0
|
|
* [2] version=1
|
|
*
|
|
* [2] subType (uint16) = 0
|
|
* (служебное поле для совместимости с единым форматом body,
|
|
* чтобы ВСЕ body имели subType одинаковым способом)
|
|
*
|
|
* [5] tag ASCII "SHiNE"
|
|
* [1] loginLength=N (uint8)
|
|
* [N] login UTF-8
|
|
*
|
|
* ЛИНИЯ:
|
|
* - строго lineIndex=0 (genesis)
|
|
*/
|
|
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 всегда 0 (служебная совместимость). */
|
|
public static final short SUBTYPE_COMPAT = 0;
|
|
|
|
public static final String TAG = "SHiNE";
|
|
|
|
public final short subType; // всегда 0
|
|
public final String tag; // "SHiNE"
|
|
public final String login;
|
|
|
|
/** Десериализация из полного bodyBytes (включая type/version/subType). */
|
|
public HeaderBody(byte[] bodyBytes) {
|
|
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
|
|
if (bodyBytes.length < 4 + 2) throw new IllegalArgumentException("HeaderBody too short (<6)");
|
|
|
|
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 HeaderBody: type=" + type + " ver=" + ver);
|
|
|
|
this.subType = bb.getShort();
|
|
if (this.subType != SUBTYPE_COMPAT)
|
|
throw new IllegalArgumentException("HeaderBody subType must be 0, got=" + (this.subType & 0xFFFF));
|
|
|
|
// дальше: tag[5] + loginLen[1] минимум
|
|
if (bb.remaining() < 5 + 1)
|
|
throw new IllegalArgumentException("Header payload too short");
|
|
|
|
byte[] tagBytes = new byte[5];
|
|
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.tag = TAG;
|
|
this.login = login;
|
|
}
|
|
|
|
@Override public short type() { return TYPE; }
|
|
@Override public short version() { return VER; }
|
|
@Override public short subType() { return subType; }
|
|
|
|
@Override
|
|
public short expectedLineIndex() {
|
|
return 0;
|
|
}
|
|
|
|
@Override
|
|
public HeaderBody check() {
|
|
if (subType != SUBTYPE_COMPAT)
|
|
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 > 255)
|
|
throw new IllegalArgumentException("Login too long (>255 bytes)");
|
|
|
|
// type[2] + ver[2] + subType[2] + tag[5] + loginLen[1] + login[N]
|
|
int cap = 2 + 2 + 2 + 5 + 1 + loginUtf8.length;
|
|
|
|
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
|
|
|
|
bb.putShort(TYPE);
|
|
bb.putShort(VER);
|
|
|
|
bb.putShort(SUBTYPE_COMPAT);
|
|
|
|
bb.put(TAG.getBytes(StandardCharsets.US_ASCII)); // [5]
|
|
bb.put((byte) loginUtf8.length); // [1]
|
|
bb.put(loginUtf8); // [N]
|
|
|
|
return bb.array();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return """
|
|
HeaderBody {
|
|
тип записи : HEADER (type=0, ver=1)
|
|
ожидаемая линия : 0 (genesis)
|
|
subType : 0 (compat)
|
|
тег формата : "%s"
|
|
login владельца : "%s"
|
|
}
|
|
""".formatted(tag, login);
|
|
}
|
|
} |