package cn.org.hentai.jtt1078.server;
|
|
import cn.org.hentai.jtt1078.util.ByteHolder;
|
import cn.org.hentai.jtt1078.util.ByteUtils;
|
import cn.org.hentai.jtt1078.util.Packet;
|
|
import java.time.Instant;
|
import java.time.ZoneOffset;
|
import java.time.ZonedDateTime;
|
import java.time.format.DateTimeFormatter;
|
import java.util.Arrays;
|
|
/**
|
* JTT1078 Protocol Decoder
|
*/
|
|
public class Jtt1078Decoder {
|
private ByteHolder buffer = new ByteHolder(4096);
|
private int simLen = 0;
|
private String sim = "";
|
private byte channel = 0;
|
private static final byte[] HEAD1078 = {0x30, 0x31, 0x63, 0x64};
|
|
public void write(byte[] block) {
|
buffer.write(block);
|
}
|
|
public void write(byte[] block, int startIndex, int length) {
|
byte[] buff = new byte[length];
|
System.arraycopy(block, startIndex, buff, 0, length);
|
write(buff);
|
}
|
|
public Packet decode() {
|
if (buffer.size() < 4) return null;
|
|
// Check header
|
if (!Arrays.equals(buffer.array(4), HEAD1078)) {
|
// log.warn("Invalid protocol header, expected:30316364, actual:{}",
|
// ByteUtils.toHexString(buffer.array(4)));
|
buffer.clear();
|
return null;
|
}
|
|
// Try to determine protocol version if not set
|
if (simLen == 0) {
|
// Try version 16 (2013 protocol)
|
Packet packet = decodePacket(16);
|
if (packet != null) {
|
Analyze analyze = parse16(packet.getBytes());
|
sim = analyze.getSim();
|
channel = analyze.getCh();
|
simLen = 6;
|
System.out.printf("%s-%d device started output%n", sim, channel);
|
return packet;
|
}
|
|
// Try version 19 (2019 protocol)
|
packet = decodePacket(19);
|
if (packet != null) {
|
Analyze analyze = parse19(packet.getBytes());
|
sim = analyze.getSim();
|
channel = analyze.getCh();
|
simLen = 10;
|
System.out.printf("%s-%d device started output%n", sim, channel);
|
return packet;
|
}
|
|
return null;
|
} else {
|
return decodePacket(simLen == 6 ? 16 : 19);
|
}
|
}
|
|
private Packet decodePacket(int version) {
|
int dataTypeIndex = version == 16 ? 15 : 19;
|
if (buffer.size() < dataTypeIndex + 1) return null;
|
|
byte dataType = (byte) ((buffer.get(dataTypeIndex) >> 4) & 0x0F);
|
int dataLenIndex;
|
|
switch (dataType) {
|
case 3:
|
dataLenIndex = dataTypeIndex + 9;
|
break;
|
case 4:
|
dataLenIndex = dataTypeIndex + 1;
|
break;
|
default:
|
dataLenIndex = dataTypeIndex + 13;
|
}
|
//log.info("dataType:"+dataType);
|
if (buffer.size() < dataLenIndex + 2) return null;
|
int dataLen = buffer.getShort(dataLenIndex) & 0xFFFF;
|
int packetLength = dataLenIndex + 2 + dataLen;
|
|
if (buffer.size() < packetLength) return null;
|
|
// Check next packet header
|
if (buffer.size() >= packetLength + 4) {
|
byte[] nextHeader = new byte[4];
|
for (int i = 0; i < 4; i++) {
|
nextHeader[i] = buffer.get(packetLength + i);
|
}
|
if (!Arrays.equals(nextHeader, HEAD1078)) {
|
// log.warn("Invalid data type: {}", dataType);
|
buffer.clear();
|
return null;
|
}
|
}
|
|
byte[] packetData = new byte[packetLength];
|
buffer.sliceInto(packetData, packetLength);
|
// printPacketInfo(packetData, version);
|
return Packet.create(packetData);
|
}
|
|
private Analyze parse16(byte[] data) {
|
Analyze analyze = new Analyze();
|
analyze.setPt((byte) (data[5] & 0x7F));
|
analyze.setSn((short) (((data[6] & 0xFF) << 8) | (data[7] & 0xFF)));
|
|
StringBuilder simBuilder = new StringBuilder();
|
for (int i = 0; i < 6; i++) {
|
simBuilder.append(nextBcd(data, 8 + i));
|
}
|
analyze.setSim(simBuilder.toString());
|
|
analyze.setCh(data[14]);
|
analyze.setDt((byte) ((data[15] >> 4) & 0x0F));
|
analyze.setFi((byte) (data[15] & 0x0F));
|
|
int dataLenIndex;
|
switch (analyze.getDt()) {
|
case 3:
|
analyze.setTimestamp(setTimestamp(data, 16));
|
dataLenIndex = 24;
|
break;
|
case 4:
|
dataLenIndex = 16;
|
break;
|
default:
|
analyze.setTimestamp(setTimestamp(data, 16));
|
analyze.setLastIInterval((short) (((data[24] & 0xFF) << 8) | (data[25] & 0xFF)));
|
analyze.setLastInterval((short) (((data[26] & 0xFF) << 8) | (data[27] & 0xFF)));
|
dataLenIndex = 28;
|
}
|
|
analyze.setDataLen((short) (((data[dataLenIndex] & 0xFF) << 8) | (data[dataLenIndex + 1] & 0xFF)));
|
byte[] payload = new byte[analyze.getDataLen()];
|
System.arraycopy(data, dataLenIndex + 2, payload, 0, payload.length);
|
analyze.setData(payload);
|
|
return analyze;
|
}
|
|
private Analyze parse19(byte[] data) {
|
Analyze analyze = new Analyze();
|
analyze.setPt((byte) (data[5] & 0x7F));
|
analyze.setSn((short) (((data[6] & 0xFF) << 8) | (data[7] & 0xFF)));
|
|
StringBuilder simBuilder = new StringBuilder();
|
for (int i = 0; i < 10; i++) {
|
simBuilder.append(nextBcd(data, 8 + i));
|
}
|
analyze.setSim(simBuilder.toString());
|
|
analyze.setCh(data[18]);
|
analyze.setDt((byte) ((data[19] >> 4) & 0x0F));
|
analyze.setFi((byte) (data[19] & 0x0F));
|
|
int dataLenIndex;
|
switch (analyze.getDt()) {
|
case 3:
|
analyze.setTimestamp(setTimestamp(data, 20));
|
dataLenIndex = 28;
|
break;
|
case 4:
|
dataLenIndex = 20;
|
break;
|
default:
|
analyze.setTimestamp(setTimestamp(data, 20));
|
analyze.setLastIInterval((short) (((data[28] & 0xFF) << 8) | (data[29] & 0xFF)));
|
analyze.setLastInterval((short) (((data[30] & 0xFF) << 8) | (data[31] & 0xFF)));
|
dataLenIndex = 32;
|
}
|
|
analyze.setDataLen((short) (((data[dataLenIndex] & 0xFF) << 8) | (data[dataLenIndex + 1] & 0xFF)));
|
byte[] payload = new byte[analyze.getDataLen()];
|
System.arraycopy(data, dataLenIndex + 2, payload, 0, payload.length);
|
analyze.setData(payload);
|
|
return analyze;
|
}
|
|
private long setTimestamp(byte[] data, int offset) {
|
long timestamp = 0;
|
for (int i = 0; i < 8; i++) {
|
timestamp = (timestamp << 8) | (data[offset + i] & 0xFF);
|
}
|
return timestamp;
|
}
|
|
private String nextBcd(byte[] data, int offset) {
|
byte val = data[offset];
|
int ch1 = (val >> 4) & 0x0F;
|
int ch2 = val & 0x0F;
|
return String.format("%d%d", ch1, ch2);
|
}
|
|
private void printPacketInfo(byte[] packet, int version) {
|
System.out.println("\n=== JTT1078 " + (version == 16 ? "2013" : "2019") + " Protocol Packet ===");
|
System.out.println("Header: " + ByteUtils.toHexString(packet, 0, 4));
|
|
Analyze analyze = version == 16 ? parse16(packet) : parse19(packet);
|
System.out.println("Payload Type: " + analyze.getPt());
|
System.out.println("Sequence Number: " + analyze.getSn());
|
System.out.println("SIM: " + analyze.getSim());
|
System.out.println("Channel: " + analyze.getCh());
|
System.out.println("Data Type: " + getDataTypeDescription(analyze.getDt()));
|
System.out.println("Frame Info: " + getFrameInfoDescription(analyze.getFi()));
|
|
if (analyze.getTimestamp() != 0) {
|
Instant instant = Instant.ofEpochMilli(analyze.getTimestamp());
|
ZonedDateTime beijingTime = instant.atZone(ZoneOffset.ofHours(8));
|
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
System.out.println("Timestamp: " + formatter.format(beijingTime));
|
}
|
|
if (analyze.getLastIInterval() != 0) {
|
System.out.println("Last I Frame Interval: " + analyze.getLastIInterval());
|
}
|
if (analyze.getLastInterval() != 0) {
|
System.out.println("Last Frame Interval: " + analyze.getLastInterval());
|
}
|
|
System.out.println("Data Length: " + analyze.getDataLen());
|
System.out.println("=================================\n");
|
}
|
|
private String getDataTypeDescription(int dataType) {
|
switch (dataType) {
|
case 0: return "Video I Frame";
|
case 1: return "Video P Frame";
|
case 2: return "Video B Frame";
|
case 3: return "Audio Frame";
|
case 4: return "Transparent Data";
|
default: return "Unknown (" + dataType + ")";
|
}
|
}
|
|
private String getFrameInfoDescription(int fi) {
|
switch (fi) {
|
case 0: return "Atomic Packet";
|
case 1: return "First Packet";
|
case 2: return "Last Packet";
|
case 3: return "Middle Packet";
|
default: return "Unknown (" + fi + ")";
|
}
|
}
|
}
|
|
class Analyze {
|
private byte pt;
|
private short sn;
|
private String sim;
|
private byte ch;
|
private byte dt;
|
private byte fi;
|
private long timestamp;
|
private short lastIInterval;
|
private short lastInterval;
|
private short dataLen;
|
private byte[] data;
|
|
// Getters and setters
|
public byte getPt() { return pt; }
|
public void setPt(byte pt) { this.pt = pt; }
|
|
public short getSn() { return sn; }
|
public void setSn(short sn) { this.sn = sn; }
|
|
public String getSim() { return sim; }
|
public void setSim(String sim) { this.sim = sim; }
|
|
public byte getCh() { return ch; }
|
public void setCh(byte ch) { this.ch = ch; }
|
|
public byte getDt() { return dt; }
|
public void setDt(byte dt) { this.dt = dt; }
|
|
public byte getFi() { return fi; }
|
public void setFi(byte fi) { this.fi = fi; }
|
|
public long getTimestamp() { return timestamp; }
|
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
|
|
public short getLastIInterval() { return lastIInterval; }
|
public void setLastIInterval(short lastIInterval) { this.lastIInterval = lastIInterval; }
|
|
public short getLastInterval() { return lastInterval; }
|
public void setLastInterval(short lastInterval) { this.lastInterval = lastInterval; }
|
|
public short getDataLen() { return dataLen; }
|
public void setDataLen(short dataLen) { this.dataLen = dataLen; }
|
|
public byte[] getData() { return data; }
|
public void setData(byte[] data) { this.data = data; }
|
}
|