Каналы: типы 0/1/100/200, CreateChannel v3, state для chat200, новые API и деплой на prod

This commit is contained in:
AidarKC
2026-05-13 01:17:23 +03:00
parent 97d37a2eb6
commit e95f65ac78
36 changed files with 1764 additions and 187 deletions
@@ -22,7 +22,9 @@ public final class BodyRecordParser {
return new HeaderBody(subType, version, bodyBytes).check();
}
if (st == (CreateChannelBody.SUBTYPE & 0xFFFF)
&& (v == (CreateChannelBody.VER & 0xFFFF) || v == (CreateChannelBody.VER2 & 0xFFFF))) {
&& (v == (CreateChannelBody.VER & 0xFFFF)
|| v == (CreateChannelBody.VER2 & 0xFFFF)
|| v == (CreateChannelBody.VER3 & 0xFFFF))) {
return new CreateChannelBody(subType, version, bodyBytes).check();
}
throw new IllegalArgumentException(
@@ -28,18 +28,38 @@ import java.util.Objects;
* [N] channelName UTF-8
* [2] channelDescriptionLen
* [M] channelDescription UTF-8 (0..200 bytes)
*
* v3 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)
* [2] channelTypeCode (uint16)
* [2] channelTypeVersion (uint16)
*/
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;
public static final short CHANNEL_TYPE_PUBLIC = 1;
public static final short CHANNEL_TYPE_PERSONAL = 100;
public static final short CHANNEL_TYPE_GROUP = 200;
public static final short CHANNEL_TYPE_VERSION_DEFAULT = 1;
private static final byte[] ZERO32 = new byte[32];
private static final int MAX_NAME_LENGTH = 32;
private static final int MAX_DESCRIPTION_UTF8_LEN = 200;
@@ -54,6 +74,8 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
public final String channelName;
public final String channelDescription;
public final short channelTypeCode;
public final short channelTypeVersion;
public CreateChannelBody(short subType, short version, byte[] bodyBytes) {
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
@@ -62,8 +84,8 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
this.version = version;
int ver = this.version & 0xFFFF;
if (ver != (VER & 0xFFFF) && ver != (VER2 & 0xFFFF)) {
throw new IllegalArgumentException("CreateChannelBody version must be 1 or 2, got=" + ver);
if (ver != (VER & 0xFFFF) && ver != (VER2 & 0xFFFF) && ver != (VER3 & 0xFFFF)) {
throw new IllegalArgumentException("CreateChannelBody version must be 1, 2 or 3, got=" + ver);
}
if ((this.subType & 0xFFFF) != (SUBTYPE & 0xFFFF)) {
throw new IllegalArgumentException("CreateChannelBody subType must be TECH_CREATE_CHANNEL(1), got=" + (this.subType & 0xFFFF));
@@ -93,9 +115,9 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
bb.get(nameBytes);
this.channelName = new String(nameBytes, StandardCharsets.UTF_8);
if (ver == (VER2 & 0xFFFF)) {
if (ver == (VER2 & 0xFFFF) || ver == (VER3 & 0xFFFF)) {
if (bb.remaining() < 2) {
throw new IllegalArgumentException("CreateChannelBody v2 missing channelDescriptionLen");
throw new IllegalArgumentException("CreateChannelBody v2/v3 missing channelDescriptionLen");
}
int descriptionLen = Short.toUnsignedInt(bb.getShort());
@@ -113,6 +135,17 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
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());
}
@@ -120,6 +153,8 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
}
this.channelDescription = "";
this.channelTypeCode = CHANNEL_TYPE_PUBLIC;
this.channelTypeVersion = CHANNEL_TYPE_VERSION_DEFAULT;
if (bb.remaining() != 0) {
throw new IllegalArgumentException("Unexpected tail bytes, remaining=" + bb.remaining());
}
@@ -142,6 +177,18 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription, VER2);
}
public CreateChannelBody(int lineCode,
int prevLineNumber,
byte[] prevLineHash32,
int thisLineNumber,
String channelName,
String channelDescription,
short channelTypeCode,
short channelTypeVersion) {
this(lineCode, prevLineNumber, prevLineHash32, thisLineNumber, channelName, channelDescription,
channelTypeCode, channelTypeVersion, VER3);
}
private CreateChannelBody(int lineCode,
int prevLineNumber,
byte[] prevLineHash32,
@@ -149,6 +196,19 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
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");
@@ -162,6 +222,8 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
this.channelName = channelName;
this.channelDescription = channelDescription == null ? "" : channelDescription;
this.channelTypeCode = channelTypeCode;
this.channelTypeVersion = channelTypeVersion;
}
@Override
@@ -188,6 +250,15 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
throw new IllegalArgumentException("channelDescription utf8 len must be <=200");
}
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");
}
@@ -219,12 +290,15 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
}
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 ? 2 + descriptionUtf8.length : 0);
int cap = 4 + (4 + 32 + 4) + 1 + nameUtf8.length
+ ((isV2 || isV3) ? 2 + descriptionUtf8.length : 0)
+ (isV3 ? 4 : 0);
ByteBuffer bb = ByteBuffer.allocate(cap).order(ByteOrder.BIG_ENDIAN);
bb.putInt(lineCode);
@@ -235,13 +309,18 @@ public final class CreateChannelBody implements BodyRecord, BodyHasLine {
bb.put((byte) nameUtf8.length);
bb.put(nameUtf8);
if (isV2) {
if (isV2 || isV3) {
bb.putShort((short) (descriptionUtf8.length & 0xFFFF));
if (descriptionUtf8.length > 0) {
bb.put(descriptionUtf8);
}
}
if (isV3) {
bb.putShort(channelTypeCode);
bb.putShort(channelTypeVersion);
}
return bb.array();
}