diff --git a/index.html b/index.html
index 614dc2c..e14751e 100644
--- a/index.html
+++ b/index.html
@@ -119,6 +119,9 @@
+
@@ -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
diff --git a/src/buffer_utils.js b/src/buffer_utils.js
index eaca13b..d5ff14f 100644
--- a/src/buffer_utils.js
+++ b/src/buffer_utils.js
@@ -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);
diff --git a/src/connection/connection.js b/src/connection/connection.js
index 68c31b0..0ac102c 100644
--- a/src/connection/connection.js
+++ b/src/connection/connection.js
@@ -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;
diff --git a/src/constants.js b/src/constants.js
index 2941cd9..6eef5b9 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -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 = {
diff --git a/src/random_utils.js b/src/random_utils.js
new file mode 100644
index 0000000..a66bad3
--- /dev/null
+++ b/src/random_utils.js
@@ -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;