Добавил поле subType и исправил мелкие баги (все тесты работают)

Дальше делать:
Описание форматов.
Запросы клиент-сервер.
Промт на клиента.

---
Потом в сервак дописать
Синхронизацию серверов.
This commit is contained in:
AidarKC
2026-01-02 16:42:15 +03:00
parent 71f1a6179c
commit ca55bfca93
6 changed files with 386 additions and 48 deletions
@@ -8,10 +8,15 @@ import java.util.Objects;
/**
* HeaderBody — type=0, version=1.
*
* Полный bodyBytes:
* Полный bodyBytes (BigEndian):
* [2] type=0
* [2] version=1
* [8] tag ASCII "SHiNE"
*
* [2] subType (uint16) = 0
* (служебное поле для совместимости с единым форматом body,
* чтобы ВСЕ body имели subType одинаковым способом)
*
* [5] tag ASCII "SHiNE"
* [1] loginLength=N (uint8)
* [N] login UTF-8
*
@@ -23,23 +28,33 @@ public final class HeaderBody implements BodyRecord {
public static final short TYPE = 0;
public static final short VER = 1;
/** Для header всегда 0 (служебная совместимость). */
public static final short SUBTYPE_COMPAT = 0;
public static final String TAG = "SHiNE";
public final String tag; // "SHiNE"
public final short subType; // всегда 0
public final String tag; // "SHiNE"
public final String login;
/** Десериализация из полного bodyBytes (включая type/version). */
/** Десериализация из полного bodyBytes (включая type/version/subType). */
public HeaderBody(byte[] bodyBytes) {
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
if (bodyBytes.length < 4) throw new IllegalArgumentException("HeaderBody too short (<4)");
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);
if (bb.remaining() < 8 + 1)
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];
@@ -55,17 +70,24 @@ public final class HeaderBody implements BodyRecord {
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() {
@@ -74,6 +96,9 @@ public final class HeaderBody implements BodyRecord {
@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_]+$"))
@@ -87,14 +112,17 @@ public final class HeaderBody implements BodyRecord {
if (loginUtf8.length > 255)
throw new IllegalArgumentException("Login too long (>255 bytes)");
int cap = 4 + 8 + 1 + loginUtf8.length;
// 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.put(TAG.getBytes(StandardCharsets.US_ASCII)); // [8]
bb.putShort(SUBTYPE_COMPAT);
bb.put(TAG.getBytes(StandardCharsets.US_ASCII)); // [5]
bb.put((byte) loginUtf8.length); // [1]
bb.put(loginUtf8); // [N]
@@ -107,6 +135,7 @@ public final class HeaderBody implements BodyRecord {
HeaderBody {
тип записи : HEADER (type=0, ver=1)
ожидаемая линия : 0 (genesis)
subType : 0 (compat)
тег формата : "%s"
login владельца : "%s"
}