kopia lustrzana https://github.com/sh123/codec2_talkie
216 wiersze
7.7 KiB
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)'`';
|
|
}
|
|
}
|