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;