Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,16 @@ The `analyzeStructure()` method provides:
- **Payload field parsing** for all supported packet types
- **Human-readable descriptions** for each field

MeshCore's packet `path_len` byte is a packed field, not a raw byte count:
- low 6 bits = number of path hashes
- high 2 bits = hash width minus 1

The decoder exposes:
- `pathLength`: raw encoded `path_len` byte
- `pathHashCount`: decoded hop-hash count
- `pathHashSize`: decoded hop-hash width in bytes
- `pathByteLength`: actual path byte length on the wire

## Ed25519 Key Derivation

The library includes MeshCore-compatible Ed25519 key derivation using the exact orlp/ed25519 algorithm via WebAssembly:
Expand Down Expand Up @@ -450,4 +460,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
41 changes: 28 additions & 13 deletions src/decoder/packet-decoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export class MeshCorePacketDecoder {
payloadType: PayloadType.RawCustom,
payloadVersion: PayloadVersion.Version1,
pathLength: 0,
pathHashCount: 0,
pathHashSize: 0,
pathByteLength: 0,
path: null,
payload: { raw: '', decoded: null },
totalBytes: bytes.length,
Expand Down Expand Up @@ -162,13 +165,19 @@ export class MeshCorePacketDecoder {
throw new Error('Packet too short for path length');
}
const pathLength = bytes[offset];
const pathHashCount = pathLength & 0x3F;
const pathHashSize = (pathLength >> 6) + 1;
if (pathHashSize === 4) {
throw new Error('Invalid path encoding: 4-byte path hashes are reserved');
}
const pathByteLength = pathHashCount * pathHashSize;

if (includeStructure) {
let pathLengthDescription = `Path contains ${pathLength} bytes`;
let pathLengthDescription = `Packed path encoding: ${pathHashCount} hop hashes x ${pathHashSize} byte(s) = ${pathByteLength} path bytes`;
if (routeType === RouteType.Direct || routeType === RouteType.TransportDirect) {
pathLengthDescription = `For "Direct" packets, this contains routing instructions. ${pathLength} bytes of routing instructions (decreases as packet travels)`;
pathLengthDescription = `Packed direct-path encoding: ${pathHashCount} hop hashes x ${pathHashSize} byte(s) = ${pathByteLength} routing bytes`;
} else if (routeType === RouteType.Flood || routeType === RouteType.TransportFlood) {
pathLengthDescription = `${pathLength} bytes showing route taken (increases as packet floods)`;
pathLengthDescription = `Packed flood-path encoding: ${pathHashCount} hop hashes x ${pathHashSize} byte(s) = ${pathByteLength} route bytes`;
}

segments.push({
Expand All @@ -181,19 +190,19 @@ export class MeshCorePacketDecoder {
}
offset += 1;

if (bytes.length < offset + pathLength) {
if (bytes.length < offset + pathByteLength) {
throw new Error('Packet too short for path data');
}

// convert path data to hex strings
const pathBytes = bytes.subarray(offset, offset + pathLength);
const path: string[] | null = pathLength > 0 ? Array.from(pathBytes).map(byteToHex) : null;
const pathBytes = bytes.subarray(offset, offset + pathByteLength);
const path: string[] | null = pathByteLength > 0 ? Array.from(pathBytes).map(byteToHex) : null;

if (includeStructure && pathLength > 0) {
if (includeStructure && pathByteLength > 0) {
if (payloadType === PayloadType.Trace) {
// TRACE packets have SNR values in path
const snrValues = [];
for (let i = 0; i < pathLength; i++) {
for (let i = 0; i < pathByteLength; i++) {
const snrRaw = bytes[offset + i];
const snrSigned = snrRaw > 127 ? snrRaw - 256 : snrRaw;
const snrDb = snrSigned / 4.0;
Expand All @@ -203,8 +212,8 @@ export class MeshCorePacketDecoder {
name: 'Path SNR Data',
description: `SNR values collected during trace: ${snrValues.join(', ')}`,
startByte: offset,
endByte: offset + pathLength - 1,
value: bytesToHex(bytes.slice(offset, offset + pathLength))
endByte: offset + pathByteLength - 1,
value: bytesToHex(bytes.slice(offset, offset + pathByteLength))
});
} else {
let pathDescription = 'Routing path information';
Expand All @@ -218,12 +227,12 @@ export class MeshCorePacketDecoder {
name: 'Path Data',
description: pathDescription,
startByte: offset,
endByte: offset + pathLength - 1,
value: bytesToHex(bytes.slice(offset, offset + pathLength))
endByte: offset + pathByteLength - 1,
value: bytesToHex(bytes.slice(offset, offset + pathByteLength))
});
}
}
offset += pathLength;
offset += pathByteLength;

// extract payload
const payloadBytes = bytes.subarray(offset);
Expand Down Expand Up @@ -359,6 +368,9 @@ export class MeshCorePacketDecoder {
payloadVersion,
transportCodes,
pathLength,
pathHashCount,
pathHashSize,
pathByteLength,
path,
payload: {
raw: payloadHex,
Expand Down Expand Up @@ -390,6 +402,9 @@ export class MeshCorePacketDecoder {
payloadType: PayloadType.RawCustom,
payloadVersion: PayloadVersion.Version1,
pathLength: 0,
pathHashCount: 0,
pathHashSize: 0,
pathByteLength: 0,
path: null,
payload: { raw: '', decoded: null },
totalBytes: bytes.length,
Expand Down
3 changes: 3 additions & 0 deletions src/types/packet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ export interface DecodedPacket {
// transport and routing
transportCodes?: [number, number];
pathLength: number;
pathHashCount: number;
pathHashSize: number;
pathByteLength: number;
path: string[] | null;

// payload data
Expand Down
6 changes: 6 additions & 0 deletions tests/ack-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ describe('Ack/Path Packet Decoding', () => {
expect(result.isValid).toBe(true);
expect(result.payloadType).toBe(PayloadType.Ack);
expect(result.pathLength).toBe(4);
expect(result.pathHashCount).toBe(4);
expect(result.pathHashSize).toBe(1);
expect(result.pathByteLength).toBe(4);

if (result.payload.decoded && 'type' in result.payload.decoded && result.payload.decoded.type === PayloadType.Ack) {
const ackPayload = result.payload.decoded as AckPayload;
Expand All @@ -35,6 +38,9 @@ describe('Ack/Path Packet Decoding', () => {
expect(result.isValid).toBe(true);
expect(result.payloadType).toBe(PayloadType.Path);
expect(result.pathLength).toBe(5); // 5 bytes in packet-level path
expect(result.pathHashCount).toBe(5);
expect(result.pathHashSize).toBe(1);
expect(result.pathByteLength).toBe(5);
expect(result.path).toEqual(['F4', '64', 'C7', '7E', '41']); // Packet-level path data
expect(result.messageHash).toBe('A574CE1D');
expect(result.totalBytes).toBe(27);
Expand Down
3 changes: 2 additions & 1 deletion tests/packet-structure.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,8 @@ describe('Packet Structure Analysis', () => {
const pathDataSegment = structure.segments.find(s => s.name === 'Path Data');

expect(pathLengthSegment).toBeDefined();
expect(pathLengthSegment!.value).toBe('0x04'); // 4 bytes of path data
expect(pathLengthSegment!.value).toBe('0x04');
expect(pathLengthSegment!.description).toContain('4 hop hashes x 1 byte(s) = 4 route bytes');

expect(pathDataSegment).toBeDefined();
expect(pathDataSegment!.value).toBe('6F17C47E'); // Path bytes
Expand Down