implement path tracing

This commit is contained in:
liamcottle 2025-03-17 23:17:03 +13:00
parent 85d82b13c1
commit 9e3171bd78
5 changed files with 131 additions and 0 deletions

View file

@ -119,6 +119,9 @@
<button @click="deleteChannel" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
DeleteChannel
</button>
<button @click="sendTrace" class="border border-gray-500 px-2 bg-gray-100 hover:bg-gray-200 rounded">
SendTrace
</button>
</div>
</div>
@ -557,6 +560,45 @@
alert("Failed to delete channel!");
}
},
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
},
async sendTrace() {
// ask user for path
const pathString = prompt("Enter path as hex string eg: 12,3f,4d");
if(!pathString){
return;
}
const pathHex = pathString.replaceAll(",", "");
const pathBytes = BufferUtils.hexToBytes(pathHex);
console.log(pathBytes);
try {
console.log(`TRACE request for path: ${pathString}`);
const response = await this.connection.tracePath(pathBytes);
console.log("TRACE response", response);
const responseList = [];
for(var i = 0; i < response.pathLen; i++){
const pathHash = response.pathHashes[i];
const pathSnr = response.pathSnrs[i] / 4;
responseList.push(`Hop ${i + 1}: hash=${pathHash.toString(16)}, snr=${pathSnr}`);
}
responseList.push(`Trace Received: snr=${response.lastSnr}`);
console.log(responseList.join("\n"));
alert(responseList.join("\n"));
} catch(e) {
console.log(e);
}
},
async login(contact) {
// ask user for password

View file

@ -6,6 +6,10 @@ class BufferUtils {
}).join('');
}
static hexToBytes(hex) {
return Uint8Array.from(hex.match(/.{1,2}/g).map((byte) => parseInt(byte, 16)));
}
static base64ToBytes(base64) {
return Uint8Array.from(atob(base64), (c) => {
return c.charCodeAt(0);

View file

@ -4,6 +4,7 @@ import Constants from "../constants.js";
import EventEmitter from "../events.js";
import BufferUtils from "../buffer_utils.js";
import Packet from "../packet.js";
import RandomUtils from "../random_utils.js";
class Connection extends EventEmitter {
@ -247,6 +248,16 @@ class Connection extends EventEmitter {
await this.sendToRadioFrame(data.toBytes());
}
async sendCommandSendTracePath(tag, auth, path) {
const data = new BufferWriter();
data.writeByte(Constants.CommandCodes.SendTracePath);
data.writeUInt32LE(tag);
data.writeUInt32LE(auth);
data.writeByte(0); // flags
data.writeBytes(path);
await this.sendToRadioFrame(data.toBytes());
}
onFrameReceived(frame) {
// emit received frame
@ -305,6 +316,8 @@ class Connection extends EventEmitter {
this.onStatusResponsePush(bufferReader);
} else if(responseCode === Constants.PushCodes.LogRxData){
this.onLogRxDataPush(bufferReader);
} else if(responseCode === Constants.PushCodes.TraceData){
this.onTraceDataPush(bufferReader);
} else {
console.log("unhandled frame", frame);
}
@ -368,6 +381,21 @@ class Connection extends EventEmitter {
});
}
onTraceDataPush(bufferReader) {
const reserved = bufferReader.readByte();
const pathLen = bufferReader.readUInt8();
this.emit(Constants.PushCodes.TraceData, {
reserved: reserved,
pathLen: pathLen,
flags: bufferReader.readUInt8(),
tag: bufferReader.readUInt32LE(),
authCode: bufferReader.readUInt32LE(),
pathHashes: bufferReader.readBytes(pathLen),
pathSnrs: bufferReader.readBytes(pathLen),
lastSnr: bufferReader.readInt8() / 4,
});
}
onOkResponse(bufferReader) {
this.emit(Constants.ResponseCodes.Ok, {
@ -1632,6 +1660,49 @@ class Connection extends EventEmitter {
});
}
tracePath(path) {
return new Promise(async (resolve, reject) => {
try {
// generate a random tag for this trace, so we can listen for the correct response
const tag = RandomUtils.getRandomInt(0, 4294967295);
// resolve promise when we receive trace data
const onTraceDataPush = (response) => {
// make sure tag matches
if(response.tag !== tag){
console.log("ignoring trace data for a different trace request");
return;
}
// resolve
this.off(Constants.PushCodes.TraceData, onTraceDataPush);
this.off(Constants.ResponseCodes.Err, onErr);
resolve(response);
}
// reject promise when we receive err
const onErr = () => {
this.off(Constants.PushCodes.TraceData, onTraceDataPush);
this.off(Constants.ResponseCodes.Err, onErr);
reject();
}
// listen for events
this.on(Constants.PushCodes.TraceData, onTraceDataPush);
this.once(Constants.ResponseCodes.Err, onErr);
// trace path
await this.sendCommandSendTracePath(tag, 0, path);
} catch(e) {
reject(e);
}
});
}
}
export default Connection;

View file

@ -41,6 +41,8 @@ class Constants {
SendStatusReq: 27, // todo
GetChannel: 31,
SetChannel: 32,
// todo sign commands
SendTracePath: 36,
}
static ResponseCodes = {
@ -73,6 +75,7 @@ class Constants {
LoginFail: 0x86, // not usable yet
StatusResponse: 0x87,
LogRxData: 0x88,
TraceData: 0x89,
}
static AdvType = {

11
src/random_utils.js Normal file
View file

@ -0,0 +1,11 @@
class RandomUtils {
static getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
}
export default RandomUtils;