codec2_talkie/codec2talkie/src/main/java/com/radio/codec2talkie/protocol/ax25/AX25Packet.java

216 wiersze
7.7 KiB
Java

package com.radio.codec2talkie.protocol.ax25;
import androidx.annotation.NonNull;
import com.radio.codec2talkie.protocol.aprs.tools.AprsIsData;
import com.radio.codec2talkie.tools.DebugTools;
import com.radio.codec2talkie.tools.TextTools;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class AX25Packet {
public static int MaximumSize = 512;
public static int MaximumRptCount = 7;
public String src;
public String dst;
public String digipath;
public boolean isAudio;
public byte[] rawData;
public boolean isValid;
private final byte AX25CTRL_UI = (byte)0x03;
private final byte AX25PID_NO_LAYER3 = (byte)0xf0;
private final byte AX25PID_AUDIO = (byte)0xf1;
public void fromBinary(byte[] data) {
isValid = false;
if (data == null) return;
// lora text packet with 0x3c,0xff,0x01 prefix
if (data.length > 3 && data[0] == (byte)0x3c && data[1] == (byte)0xff && data[2] == (byte)0x01) {
String rawText = new String(Arrays.copyOfRange(data, 3, data.length), StandardCharsets.US_ASCII);
AprsIsData textPacket = AprsIsData.fromString(rawText);
if (textPacket != null) {
src = textPacket.src;
dst = textPacket.dst;
digipath = textPacket.rawDigipath;
rawData = textPacket.data.getBytes(StandardCharsets.US_ASCII);
isAudio = false;
isValid = true;
return;
}
}
// binary packet
ByteBuffer buffer = ByteBuffer.wrap(data);
try {
// dst
AX25Callsign dstCallsign = new AX25Callsign();
byte[] dstBytes = new byte[AX25Callsign.CallsignMaxSize];
buffer.get(dstBytes);
dstCallsign.fromBinary(dstBytes);
if (!dstCallsign.isValid) return;
dst = dstCallsign.toString();
// src
AX25Callsign srcCallsign = new AX25Callsign();
byte[] srcBytes = new byte[AX25Callsign.CallsignMaxSize];
buffer.get(srcBytes);
srcCallsign.fromBinary(srcBytes);
if (!srcCallsign.isValid) return;
src = srcCallsign.toString();
// digipath
if (!srcCallsign.isLast) {
StringBuilder rptBuilder = new StringBuilder();
for (int i = 0; i < MaximumRptCount; i++) {
AX25Callsign rptCallsign = new AX25Callsign();
byte[] rptBytes = new byte[AX25Callsign.CallsignMaxSize];
buffer.get(rptBytes);
rptCallsign.fromBinary(rptBytes);
if (!rptCallsign.isValid) return;
rptBuilder.append(rptCallsign.toString());
if (rptCallsign.isLast) break;
rptBuilder.append(',');
}
digipath = rptBuilder.toString();
}
// ctrl, UI
byte ax25Ctrl = buffer.get();
if (ax25Ctrl != AX25CTRL_UI) return;
// pid, isAudio
byte ax25Pid = buffer.get();
if (ax25Pid == AX25PID_AUDIO) {
isAudio = true;
} else if (ax25Pid == AX25PID_NO_LAYER3) {
isAudio = false;
} else {
return;
}
// rawData
rawData = new byte[buffer.remaining()];
buffer.get(rawData);
isValid = true;
} catch (BufferUnderflowException e) {
//e.printStackTrace();
}
}
public byte[] toTextBinary() {
byte[] packetContent = toString().getBytes(StandardCharsets.US_ASCII);
// lora aprs prefix 0x3c,0xff,0x01
ByteBuffer textPacketBuffer = ByteBuffer.allocateDirect(packetContent.length + 3);
textPacketBuffer.put((byte)0x3c).put((byte)0xff).put((byte)0x01);
textPacketBuffer.put(packetContent);
textPacketBuffer.flip();
byte[] ax25Frame = new byte[textPacketBuffer.remaining()];
textPacketBuffer.get(ax25Frame);
return ax25Frame;
}
public byte[] toBinary() {
ByteBuffer buffer = ByteBuffer.allocate(MaximumSize);
String[] rptCallsigns = new String[] {};
if (digipath != null)
rptCallsigns = digipath.replaceAll(" ", "").split(",");
boolean hasRtpCallsigns = rptCallsigns.length > 0 &&
!(rptCallsigns.length == 1 && rptCallsigns[0].length() == 0);
// dst
AX25Callsign dstCallsign = new AX25Callsign();
dstCallsign.fromString(dst);
if (!dstCallsign.isValid) return null;
buffer.put(dstCallsign.toBinary());
// src
AX25Callsign srcCallsign = new AX25Callsign();
srcCallsign.fromString(src);
if (!srcCallsign.isValid) return null;
if (!hasRtpCallsigns) srcCallsign.isLast = true;
buffer.put(srcCallsign.toBinary());
// digipath
for (int i = 0; i < rptCallsigns.length; i++) {
String callsign = rptCallsigns[i];
if (callsign.length() == 0) continue;
AX25Callsign digiCallsign = new AX25Callsign();
digiCallsign.fromString(callsign);
if (!digiCallsign.isValid) return null;
digiCallsign.isLast = (i == rptCallsigns.length - 1);
buffer.put(digiCallsign.toBinary());
}
// flags
buffer.put(AX25CTRL_UI);
if (isAudio) {
buffer.put(AX25PID_AUDIO);
} else {
buffer.put(AX25PID_NO_LAYER3);
}
// data
buffer.put(rawData);
// return
buffer.flip();
byte[] b = new byte[buffer.remaining()];
buffer.get(b);
return b;
}
public boolean digiRepeat() {
boolean isDigiRepeated = false;
if (!isValid) return false;
if (digipath == null) return digiRepeatMicE();
String[] digiPaths = digipath.split(",");
if (digiPaths.length == 0) return digiRepeatMicE();
StringBuilder buf = new StringBuilder();
for (int i = 0; i < digiPaths.length; i++) {
AX25Callsign rptCallsign = new AX25Callsign();
rptCallsign.fromString(digiPaths[i]);
if (rptCallsign.isValid) {
if (!isDigiRepeated && rptCallsign.digiRepeat()) {
isDigiRepeated = true;
buf.append(rptCallsign.toString());
} else {
buf.append(digiPaths[i]);
}
} else {
buf.append(digiPaths[i]);
}
if (i < digiPaths.length - 1) buf.append(",");
}
digipath = buf.toString();
return isDigiRepeated;
}
@NonNull
public String toString() {
String path = digipath == null ? "" : digipath;
if (!path.isEmpty())
path = "," + path;
String info = DebugTools.bytesToDebugString(rawData);
if (!isAudio) {
info = DebugTools.bytesToDebugString(TextTools.stripNulls(rawData));
}
return String.format("%s>%s%s:%s", src, dst, path, info);
}
private boolean digiRepeatMicE() {
if (!isMicE()) return false;
AX25Callsign dstRptCallsign = new AX25Callsign();
dstRptCallsign.fromString(dst);
if (dstRptCallsign.isValid) {
boolean isSuccess = dstRptCallsign.digiRepeatCallsign();
if (isSuccess) {
dst = dstRptCallsign.toString();
return true;
}
}
return false;
}
private boolean isMicE() {
return !isAudio &&
rawData != null &&
rawData.length > 0 &&
rawData[0] == (byte)'`';
}
}