SHA256
88 lines
4.9 KiB
Java
88 lines
4.9 KiB
Java
package blockchain.body;
|
|
|
|
import blockchain.MsgSubType;
|
|
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.ByteOrder;
|
|
import java.nio.charset.CharacterCodingException;
|
|
import java.nio.charset.CodingErrorAction;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.Arrays;
|
|
import java.util.Objects;
|
|
|
|
/** STATUS_ACTION. External target = login + forkNumber + blockNumber + hash32. */
|
|
public final class StatusActionBody implements BodyRecord, BodyHasTarget {
|
|
public static final short TYPE = 5;
|
|
public static final short VER = 1;
|
|
public static final int KEY = ((TYPE & 0xFFFF) << 16) | (VER & 0xFFFF);
|
|
|
|
public final short subType;
|
|
public final short version;
|
|
public final String toLogin;
|
|
public final int toForkNumber;
|
|
public final int toBlockGlobalNumber;
|
|
public final byte[] toBlockHash32;
|
|
public final String message;
|
|
|
|
public StatusActionBody(short subType, short version, byte[] bodyBytes) {
|
|
Objects.requireNonNull(bodyBytes, "bodyBytes == null");
|
|
this.subType = subType; this.version = version;
|
|
if ((version & 0xFFFF) != 1 || !isSupportedSubType(subType)) throw new IllegalArgumentException("Bad STATUS_ACTION header");
|
|
ByteBuffer bb = ByteBuffer.wrap(bodyBytes).order(ByteOrder.BIG_ENDIAN);
|
|
BodyTargetSupport.ExternalTarget t = BodyTargetSupport.readExternal(bb, "STATUS_ACTION", 2);
|
|
this.toLogin = t.login(); this.toForkNumber = t.forkNumber(); this.toBlockGlobalNumber = t.blockNumber(); this.toBlockHash32 = t.blockHash32();
|
|
this.message = readText(bb);
|
|
if (bb.hasRemaining()) throw new IllegalArgumentException("Unexpected STATUS_ACTION tail");
|
|
}
|
|
|
|
public StatusActionBody(short subType, String toLogin, int toForkNumber, int toBlockGlobalNumber, byte[] toBlockHash32, String message) {
|
|
Objects.requireNonNull(message, "message == null");
|
|
if (!isSupportedSubType(subType)) throw new IllegalArgumentException("Unsupported STATUS_ACTION subType");
|
|
BodyTargetSupport.validateExternal(toLogin, toForkNumber, toBlockGlobalNumber, toBlockHash32, "STATUS_ACTION");
|
|
this.subType = subType; this.version = VER; this.toLogin = toLogin; this.toForkNumber = toForkNumber;
|
|
this.toBlockGlobalNumber = toBlockGlobalNumber; this.toBlockHash32 = Arrays.copyOf(toBlockHash32, 32); this.message = message;
|
|
}
|
|
|
|
@Override public StatusActionBody check() {
|
|
if (!isSupportedSubType(subType)) throw new IllegalArgumentException("Bad STATUS_ACTION subType");
|
|
BodyTargetSupport.validateExternal(toLogin, toForkNumber, toBlockGlobalNumber, toBlockHash32, "STATUS_ACTION");
|
|
if (message == null) throw new IllegalArgumentException("message is null");
|
|
if (message.getBytes(StandardCharsets.UTF_8).length > 65535) throw new IllegalArgumentException("Text too long");
|
|
return this;
|
|
}
|
|
|
|
@Override public byte[] toBytes() {
|
|
check();
|
|
byte[] text = message.getBytes(StandardCharsets.UTF_8);
|
|
ByteBuffer bb = ByteBuffer.allocate(BodyTargetSupport.externalSize(toLogin) + 2 + text.length).order(ByteOrder.BIG_ENDIAN);
|
|
BodyTargetSupport.writeExternal(bb, toLogin, toForkNumber, toBlockGlobalNumber, toBlockHash32, "STATUS_ACTION");
|
|
bb.putShort((short) text.length).put(text);
|
|
return bb.array();
|
|
}
|
|
|
|
@Override public String toLogin() { return toLogin; }
|
|
@Override public Integer toForkNumber() { return toForkNumber; }
|
|
@Override public Integer toBlockGlobalNumber() { return toBlockGlobalNumber; }
|
|
@Override public byte[] toBlockHashBytes() { return Arrays.copyOf(toBlockHash32, 32); }
|
|
|
|
private static boolean isSupportedSubType(short subType) {
|
|
int st = subType & 0xFFFF;
|
|
return st == (MsgSubType.STATUS_DONE_ONCE & 0xFFFF) || st == (MsgSubType.STATUS_LEARNED & 0xFFFF)
|
|
|| st == (MsgSubType.STATUS_SERVICE_PASSED & 0xFFFF) || st == (MsgSubType.STATUS_CONFIRMED & 0xFFFF)
|
|
|| st == (MsgSubType.STATUS_INTERESTED & 0xFFFF) || st == (MsgSubType.STATUS_STARTED & 0xFFFF)
|
|
|| st == (MsgSubType.STATUS_IN_STUDY & 0xFFFF) || st == (MsgSubType.STATUS_ABANDONED & 0xFFFF)
|
|
|| st == (MsgSubType.STATUS_COMPLETED & 0xFFFF);
|
|
}
|
|
|
|
private static String readText(ByteBuffer bb) {
|
|
if (bb.remaining() < 2) throw new IllegalArgumentException("STATUS_ACTION text length missing");
|
|
int len = Short.toUnsignedInt(bb.getShort());
|
|
if (bb.remaining() < len) throw new IllegalArgumentException("STATUS_ACTION text payload too short");
|
|
if (len == 0) return "";
|
|
byte[] bytes = new byte[len]; bb.get(bytes);
|
|
var dec = StandardCharsets.UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT);
|
|
try { return dec.decode(ByteBuffer.wrap(bytes)).toString(); }
|
|
catch (CharacterCodingException e) { throw new IllegalArgumentException("STATUS_ACTION text invalid UTF-8", e); }
|
|
}
|
|
}
|