CreateChannel: оставить единый актуальный формат, убрать legacy v2/v3
This commit is contained in:
parent
e95f65ac78
commit
ddeaf82bfd
@ -1,7 +1,7 @@
|
||||
# Типы каналов и CreateChannel v3
|
||||
# Типы каналов и CreateChannel
|
||||
|
||||
## 1. Формат `CreateChannelBody v3`
|
||||
Формат `TECH_CREATE_CHANNEL` поддерживает `version=3` и включает:
|
||||
## 1. Формат `CreateChannelBody`
|
||||
Формат `TECH_CREATE_CHANNEL` поддерживает единственный текущий `version=1` и включает:
|
||||
|
||||
1. line-поля канала (`lineCode`, `prevLineNumber`, `prevLineHash32`, `thisLineNumber`);
|
||||
2. `channelName`;
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
## 2026-05-13 00:02:32 +0300
|
||||
- Базовый коммит-ориентир: `f63f40f1eb2f`.
|
||||
- Добавлен `CreateChannelBody v3` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`.
|
||||
- Добавлен текущий формат `CreateChannelBody` с полями `channelType (2 байта)` и `channelTypeVersion (2 байта)`.
|
||||
- Зафиксированы типы каналов: `0=stories`, `1=public`, `100=personal`, `200=group`.
|
||||
- Серверная уникальность имени канала изменена на `owner + type + name(slug)`.
|
||||
- Root-канал `0` переименован в `stories` на уровне API-чтения.
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
Этот набор файлов — актуальная документация по текущему формату блокчейна SHiNE для каналов, их типов и командных сообщений.
|
||||
|
||||
## Оглавление
|
||||
1. [01_Channel_Types_and_CreateChannel_v3.md](./01_Channel_Types_and_CreateChannel_v3.md)
|
||||
Формат `CreateChannelBody v3`, типы каналов, уникальность имён и правила `stories`.
|
||||
1. [01_Channel_Types_and_CreateChannel.md](./01_Channel_Types_and_CreateChannel.md)
|
||||
Текущий формат `CreateChannelBody`, типы каналов, уникальность имён и правила `stories`.
|
||||
2. [02_Channel_Commands.md](./02_Channel_Commands.md)
|
||||
Командные сообщения в каналах: `/.desc`, `/.add`, `/.remove`.
|
||||
3. [CHANGELOG.md](./CHANGELOG.md)
|
||||
|
||||
@ -42,7 +42,7 @@ const MSG_SUBTYPE_REACTION_LIKE = 1;
|
||||
const MSG_SUBTYPE_REACTION_UNLIKE = 2;
|
||||
const MSG_SUBTYPE_CONNECTION_FOLLOW = 30;
|
||||
const MSG_SUBTYPE_CONNECTION_UNFOLLOW = 31;
|
||||
const CREATE_CHANNEL_BODY_VERSION = 3;
|
||||
const CREATE_CHANNEL_BODY_VERSION = 1;
|
||||
const CHANNEL_TYPE_STORIES = 0;
|
||||
const CHANNEL_TYPE_PUBLIC = 1;
|
||||
const CHANNEL_TYPE_PERSONAL = 100;
|
||||
@ -409,7 +409,7 @@ function normalizeChannelDescription(value) {
|
||||
return text;
|
||||
}
|
||||
|
||||
function makeCreateChannelBodyV3Bytes({
|
||||
function makeCreateChannelBodyBytes({
|
||||
lineCode,
|
||||
prevLineNumber,
|
||||
prevLineHashHex,
|
||||
@ -1080,7 +1080,7 @@ export class AuthService {
|
||||
msgType: MSG_TYPE_TECH,
|
||||
msgSubType: MSG_SUBTYPE_TECH_CREATE_CHANNEL,
|
||||
msgVersion: CREATE_CHANNEL_BODY_VERSION,
|
||||
bodyBytes: makeCreateChannelBodyV3Bytes({
|
||||
bodyBytes: makeCreateChannelBodyBytes({
|
||||
lineCode: 0,
|
||||
prevLineNumber,
|
||||
prevLineHashHex,
|
||||
|
||||
@ -16,15 +16,13 @@ public final class BodyRecordParser {
|
||||
int v = version & 0xFFFF;
|
||||
int st = subType & 0xFFFF;
|
||||
|
||||
// TECH supports Header v1 and CreateChannel v1/v2.
|
||||
// TECH supports Header v1 and CreateChannel current format (ver=1).
|
||||
if (t == (CreateChannelBody.TYPE & 0xFFFF)) {
|
||||
if (st == (HeaderBody.SUBTYPE_COMPAT & 0xFFFF) && v == (HeaderBody.VER & 0xFFFF)) {
|
||||
return new HeaderBody(subType, version, bodyBytes).check();
|
||||
}
|
||||
if (st == (CreateChannelBody.SUBTYPE & 0xFFFF)
|
||||
&& (v == (CreateChannelBody.VER & 0xFFFF)
|
||||
|| v == (CreateChannelBody.VER2 & 0xFFFF)
|
||||
|| v == (CreateChannelBody.VER3 & 0xFFFF))) {
|
||||
&& (v == (CreateChannelBody.VER & 0xFFFF))) {
|
||||
return new CreateChannelBody(subType, version, bodyBytes).check();
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
|
||||
@ -11,25 +11,7 @@ import java.util.Objects;
|
||||
/**
|
||||
* TECH body for create channel.
|
||||
*
|
||||
* v1 body bytes:
|
||||
* [4] lineCode
|
||||
* [4] prevLineNumber
|
||||
* [32] prevLineHash32
|
||||
* [4] thisLineNumber
|
||||
* [1] channelNameLen
|
||||
* [N] channelName UTF-8
|
||||
*
|
||||
* v2 body bytes:
|
||||
* [4] lineCode
|
||||
* [4] prevLineNumber
|
||||
* [32] prevLineHash32
|
||||
* [4] thisLineNumber
|
||||
* [1] channelNameLen
|
||||
* [N] channelName UTF-8
|
||||
* [2] channelDescriptionLen
|
||||
* [M] channelDescription UTF-8 (0..200 bytes)
|
||||
*
|
||||
* v3 body bytes:
|
||||
* body bytes:
|
||||
* [4] lineCode
|
||||
* [4] prevLineNumber
|
||||
* [32] prevLineHash32
|
||||
@ -45,13 +27,7 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
||||
|
||||
public static final short TYPE = 0;
|
||||
public static final short VER = 1;
|
||||
public static final short VER2 = 2;
|
||||
public static final short VER3 = 3;
|
||||
|
||||
public static final int KEY = ((TYPE & 0xFFFF) << 16) | (VER & 0xFFFF);
|
||||
public static final int KEY_V2 = ((TYPE & 0xFFFF) << 16) | (VER2 & 0xFFFF);
|
||||
public static final int KEY_V3 = ((TYPE & 0xFFFF) << 16) | (VER3 & 0xFFFF);
|
||||
|
||||
public static final short SUBTYPE = MsgSubType.TECH_CREATE_CHANNEL;
|
||||
|
||||
public static final short CHANNEL_TYPE_STORIES = 0;
|
||||
@ -79,35 +55,30 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
||||
|
||||
public CreateChannelBody(short subType, short version, byte[] bodyBytes) {
|
||||
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
|
||||
|
||||
this.subType = subType;
|
||||
this.version = version;
|
||||
|
||||
int ver = this.version & 0xFFFF;
|
||||
if (ver != (VER & 0xFFFF) && ver != (VER2 & 0xFFFF) && ver != (VER3 & 0xFFFF)) {
|
||||
throw new IllegalArgumentException("CreateChannelBody version must be 1, 2 or 3, got=" + ver);
|
||||
if (ver != (VER & 0xFFFF)) {
|
||||
throw new IllegalArgumentException("CreateChannelBody version must be 1, got=" + ver);
|
||||
}
|
||||
if ((this.subType & 0xFFFF) != (SUBTYPE & 0xFFFF)) {
|
||||
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1), got=" + (this.subType & 0xFFFF));
|
||||
}
|
||||
|
||||
if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1) {
|
||||
if (bodyBytes.length < 4 + (4 + 32 + 4) + 1 + 1 + 2 + 4) {
|
||||
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];
|
||||
bb.get(this.prevLineHash32);
|
||||
|
||||
this.thisLineNumber = bb.getInt();
|
||||
|
||||
int nameLen = Byte.toUnsignedInt(bb.get());
|
||||
if (nameLen <= 0) throw new IllegalArgumentException("channelNameLen is 0");
|
||||
if (bb.remaining() < nameLen) {
|
||||
if (bb.remaining() < nameLen + 2 + 4) {
|
||||
throw new IllegalArgumentException("CreateChannelBody tail mismatch: remaining=" + bb.remaining() + " nameLen=" + nameLen);
|
||||
}
|
||||
|
||||
@ -115,68 +86,27 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
||||
bb.get(nameBytes);
|
||||
this.channelName = new String(nameBytes, StandardCharsets.UTF_8);
|
||||
|
||||
if (ver == (VER2 & 0xFFFF) || ver == (VER3 & 0xFFFF)) {
|
||||
if (bb.remaining() < 2) {
|
||||
throw new IllegalArgumentException("CreateChannelBody v2/v3 missing channelDescriptionLen");
|
||||
}
|
||||
|
||||
int descriptionLen = Short.toUnsignedInt(bb.getShort());
|
||||
if (descriptionLen > MAX_DESCRIPTION_UTF8_LEN) {
|
||||
throw new IllegalArgumentException("channelDescription utf8 len must be <=200");
|
||||
}
|
||||
if (bb.remaining() != descriptionLen) {
|
||||
throw new IllegalArgumentException("CreateChannelBody v2 tail mismatch: remaining=" + bb.remaining() + " descriptionLen=" + descriptionLen);
|
||||
}
|
||||
|
||||
if (descriptionLen == 0) {
|
||||
this.channelDescription = "";
|
||||
} else {
|
||||
byte[] descriptionBytes = new byte[descriptionLen];
|
||||
bb.get(descriptionBytes);
|
||||
this.channelDescription = normalizeDescription(new String(descriptionBytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
if (ver == (VER3 & 0xFFFF)) {
|
||||
if (bb.remaining() < 4) {
|
||||
throw new IllegalArgumentException("CreateChannelBody v3 missing channelTypeCode/channelTypeVersion");
|
||||
}
|
||||
this.channelTypeCode = bb.getShort();
|
||||
this.channelTypeVersion = bb.getShort();
|
||||
} else {
|
||||
this.channelTypeCode = CHANNEL_TYPE_PUBLIC;
|
||||
this.channelTypeVersion = CHANNEL_TYPE_VERSION_DEFAULT;
|
||||
}
|
||||
|
||||
if (bb.remaining() != 0) {
|
||||
throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
||||
}
|
||||
return;
|
||||
int descriptionLen = Short.toUnsignedInt(bb.getShort());
|
||||
if (descriptionLen > MAX_DESCRIPTION_UTF8_LEN) {
|
||||
throw new IllegalArgumentException("channelDescription utf8 len must be <=200");
|
||||
}
|
||||
|
||||
this.channelDescription = "";
|
||||
this.channelTypeCode = CHANNEL_TYPE_PUBLIC;
|
||||
this.channelTypeVersion = CHANNEL_TYPE_VERSION_DEFAULT;
|
||||
if (bb.remaining() != descriptionLen + 4) {
|
||||
throw new IllegalArgumentException("CreateChannelBody tail mismatch: remaining=" + bb.remaining() + " descriptionLen=" + descriptionLen);
|
||||
}
|
||||
if (descriptionLen == 0) {
|
||||
this.channelDescription = "";
|
||||
} else {
|
||||
byte[] descriptionBytes = new byte[descriptionLen];
|
||||
bb.get(descriptionBytes);
|
||||
this.channelDescription = normalizeDescription(new String(descriptionBytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
this.channelTypeCode = bb.getShort();
|
||||
this.channelTypeVersion = bb.getShort();
|
||||
if (bb.remaining() != 0) {
|
||||
throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
|
||||
}
|
||||
}
|
||||
|
||||
public CreateChannelBody(int lineCode,
|
||||
int prevLineNumber,
|
||||
byte[] prevLineHash32,
|
||||
int thisLineNumber,
|
||||
String channelName) {
|
||||
this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, "", VER);
|
||||
}
|
||||
|
||||
public CreateChannelBody(int lineCode,
|
||||
int prevLineNumber,
|
||||
byte[] prevLineHash32,
|
||||
int thisLineNumber,
|
||||
String channelName,
|
||||
String channelDescription) {
|
||||
this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription, VER2);
|
||||
}
|
||||
|
||||
public CreateChannelBody(int lineCode,
|
||||
int prevLineNumber,
|
||||
byte[] prevLineHash32,
|
||||
@ -185,41 +115,15 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
||||
String channelDescription,
|
||||
short channelTypeCode,
|
||||
short channelTypeVersion) {
|
||||
this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription,
|
||||
channelTypeCode, channelTypeVersion, VER3);
|
||||
}
|
||||
|
||||
private CreateChannelBody(int lineCode,
|
||||
int prevLineNumber,
|
||||
byte[] prevLineHash32,
|
||||
int thisLineNumber,
|
||||
String channelName,
|
||||
String channelDescription,
|
||||
short version) {
|
||||
this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription,
|
||||
CHANNEL_TYPE_PUBLIC, CHANNEL_TYPE_VERSION_DEFAULT, version);
|
||||
}
|
||||
|
||||
private CreateChannelBody(int lineCode,
|
||||
int prevLineNumber,
|
||||
byte[] prevLineHash32,
|
||||
int thisLineNumber,
|
||||
String channelName,
|
||||
String channelDescription,
|
||||
short channelTypeCode,
|
||||
short channelTypeVersion,
|
||||
short version) {
|
||||
Objects.requireNonNull(channelName, "channelName == null");
|
||||
if (lineCode < 0) throw new IllegalArgumentException("lineCode < 0");
|
||||
|
||||
this.subType = SUBTYPE;
|
||||
this.version = version;
|
||||
|
||||
this.version = VER;
|
||||
this.lineCode = lineCode;
|
||||
this.prevLineNumber = prevLineNumber;
|
||||
this.prevLineHash32 = (prevLineHash32 == null ? ZERO32 : Arrays.copyOf(prevLineHash32, 32));
|
||||
this.thisLineNumber = thisLineNumber;
|
||||
|
||||
this.channelName = channelName;
|
||||
this.channelDescription = channelDescription == null ? "" : channelDescription;
|
||||
this.channelTypeCode = channelTypeCode;
|
||||
@ -229,20 +133,14 @@ 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)");
|
||||
}
|
||||
|
||||
String normalizedName = normalizeDisplayName(channelName);
|
||||
if (normalizedName.isEmpty()) {
|
||||
throw new IllegalArgumentException("channelName is blank");
|
||||
}
|
||||
|
||||
if (normalizedName.isEmpty()) throw new IllegalArgumentException("channelName is blank");
|
||||
int cpLen = normalizedName.codePointCount(0, normalizedName.length());
|
||||
if (cpLen > MAX_NAME_LENGTH) {
|
||||
throw new IllegalArgumentException("channelName length must be <=32");
|
||||
}
|
||||
if (cpLen > MAX_NAME_LENGTH) throw new IllegalArgumentException("channelName length must be <=32");
|
||||
|
||||
String normalizedDescription = normalizeDescription(channelDescription);
|
||||
byte[] descUtf8 = normalizedDescription.getBytes(StandardCharsets.UTF_8);
|
||||
@ -252,23 +150,12 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
||||
|
||||
int typeCode = Short.toUnsignedInt(channelTypeCode);
|
||||
int typeVer = Short.toUnsignedInt(channelTypeVersion);
|
||||
if (typeCode < 0 || typeCode > 0xFFFF) {
|
||||
throw new IllegalArgumentException("channelTypeCode invalid");
|
||||
}
|
||||
if (typeVer < 0 || typeVer > 0xFFFF) {
|
||||
throw new IllegalArgumentException("channelTypeVersion invalid");
|
||||
}
|
||||
|
||||
if (prevLineNumber < 0) {
|
||||
throw new IllegalArgumentException("prevLineNumber must be >=0 for CreateChannelBody");
|
||||
}
|
||||
if (prevLineHash32 == null || prevLineHash32.length != 32) {
|
||||
throw new IllegalArgumentException("prevLineHash32 invalid");
|
||||
}
|
||||
if (thisLineNumber <= 0) {
|
||||
throw new IllegalArgumentException("thisLineNumber must be >=1 for CreateChannelBody");
|
||||
}
|
||||
if (typeCode < 0 || typeCode > 0xFFFF) throw new IllegalArgumentException("channelTypeCode invalid");
|
||||
if (typeVer < 0 || typeVer > 0xFFFF) throw new IllegalArgumentException("channelTypeVersion invalid");
|
||||
|
||||
if (prevLineNumber < 0) throw new IllegalArgumentException("prevLineNumber must be >=0 for CreateChannelBody");
|
||||
if (prevLineHash32 == null || prevLineHash32.length != 32) throw new IllegalArgumentException("prevLineHash32 invalid");
|
||||
if (thisLineNumber <= 0) throw new IllegalArgumentException("thisLineNumber must be >=1 for CreateChannelBody");
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -288,53 +175,33 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
|
||||
if (nameUtf8.length == 0 || nameUtf8.length > 255) {
|
||||
throw new IllegalArgumentException("channelName utf8 len must be 1..255");
|
||||
}
|
||||
|
||||
boolean isV2 = (version & 0xFFFF) == (VER2 & 0xFFFF);
|
||||
boolean isV3 = (version & 0xFFFF) == (VER3 & 0xFFFF);
|
||||
byte[] descriptionUtf8 = normalizeDescription(channelDescription).getBytes(StandardCharsets.UTF_8);
|
||||
if (descriptionUtf8.length > MAX_DESCRIPTION_UTF8_LEN) {
|
||||
throw new IllegalArgumentException("channelDescription utf8 len must be <=200");
|
||||
}
|
||||
|
||||
int cap = 4 + (4 + 32 + 4) + 1 + nameUtf8.length
|
||||
+ ((isV2 || isV3) ? 2 + descriptionUtf8.length : 0)
|
||||
+ (isV3 ? 4 : 0);
|
||||
int cap = 4 + (4 + 32 + 4) + 1 + nameUtf8.length + 2 + descriptionUtf8.length + 4;
|
||||
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);
|
||||
|
||||
bb.put((byte) nameUtf8.length);
|
||||
bb.put(nameUtf8);
|
||||
|
||||
if (isV2 || isV3) {
|
||||
bb.putShort((short) (descriptionUtf8.length & 0xFFFF));
|
||||
if (descriptionUtf8.length > 0) {
|
||||
bb.put(descriptionUtf8);
|
||||
}
|
||||
}
|
||||
|
||||
if (isV3) {
|
||||
bb.putShort(channelTypeCode);
|
||||
bb.putShort(channelTypeVersion);
|
||||
}
|
||||
|
||||
bb.putShort((short) (descriptionUtf8.length & 0xFFFF));
|
||||
if (descriptionUtf8.length > 0) bb.put(descriptionUtf8);
|
||||
bb.putShort(channelTypeCode);
|
||||
bb.putShort(channelTypeVersion);
|
||||
return bb.array();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lineCode() { return lineCode; }
|
||||
|
||||
@Override
|
||||
public int prevLineBlockGlobalNumber() { return prevLineNumber; }
|
||||
|
||||
@Override
|
||||
public byte[] prevLineBlockHash32() {
|
||||
return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32);
|
||||
}
|
||||
|
||||
public byte[] prevLineBlockHash32() { return prevLineHash32 == null ? null : Arrays.copyOf(prevLineHash32, 32); }
|
||||
@Override
|
||||
public int lineSeq() { return thisLineNumber; }
|
||||
}
|
||||
|
||||
|
||||
@ -105,7 +105,10 @@ public class IT_03_AddBlock_NoAuth {
|
||||
sender1.send(new CreateChannelBody(
|
||||
0, // lineCode TECH
|
||||
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
||||
"News"
|
||||
"News",
|
||||
"",
|
||||
CreateChannelBody.CHANNEL_TYPE_PUBLIC,
|
||||
CreateChannelBody.CHANNEL_TYPE_VERSION_DEFAULT
|
||||
), t);
|
||||
|
||||
newsRootBlock = st1.lastBlockNumber(); // root канала = blockNumber этого CREATE_CHANNEL
|
||||
@ -286,4 +289,4 @@ public class IT_03_AddBlock_NoAuth {
|
||||
toBlockHash32
|
||||
), timeout);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user