mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-05-02 11:33:05 +02:00
First wave of work
This commit is contained in:
@@ -52,7 +52,7 @@ export function PathModal({
|
||||
const resolvedPaths = hasPaths
|
||||
? paths.map((p) => ({
|
||||
...p,
|
||||
resolved: resolvePath(p.path, senderInfo, contacts, config),
|
||||
resolved: resolvePath(p.path, senderInfo, contacts, config, p.path_len),
|
||||
}))
|
||||
: [];
|
||||
|
||||
@@ -90,7 +90,7 @@ export function PathModal({
|
||||
{/* Raw path summary */}
|
||||
<div className="text-sm">
|
||||
{paths.map((p, index) => {
|
||||
const hops = parsePathHops(p.path);
|
||||
const hops = parsePathHops(p.path, p.path_len);
|
||||
const rawPath = hops.length > 0 ? hops.join('->') : 'direct';
|
||||
return (
|
||||
<div key={index}>
|
||||
|
||||
@@ -60,6 +60,10 @@ describe('parsePathHops', () => {
|
||||
expect(parsePathHops('1A2B3C')).toEqual(['1A', '2B', '3C']);
|
||||
});
|
||||
|
||||
it('parses multi-byte hops when path length is provided', () => {
|
||||
expect(parsePathHops('1A2B3C4D', 2)).toEqual(['1A2B', '3C4D']);
|
||||
});
|
||||
|
||||
it('converts to uppercase', () => {
|
||||
expect(parsePathHops('1a2b')).toEqual(['1A', '2B']);
|
||||
});
|
||||
@@ -197,6 +201,29 @@ describe('resolvePath', () => {
|
||||
expect(result.receiver.prefix).toBe('FF');
|
||||
});
|
||||
|
||||
it('resolves multi-byte hop prefixes when path length is provided', () => {
|
||||
const wideContacts = [
|
||||
createContact({
|
||||
public_key: '1A2B' + 'A'.repeat(60),
|
||||
name: 'WideRepeater1',
|
||||
type: CONTACT_TYPE_REPEATER,
|
||||
}),
|
||||
createContact({
|
||||
public_key: '3C4D' + 'B'.repeat(60),
|
||||
name: 'WideRepeater2',
|
||||
type: CONTACT_TYPE_REPEATER,
|
||||
}),
|
||||
];
|
||||
|
||||
const result = resolvePath('1A2B3C4D', sender, wideContacts, config, 2);
|
||||
|
||||
expect(result.hops).toHaveLength(2);
|
||||
expect(result.hops[0].prefix).toBe('1A2B');
|
||||
expect(result.hops[0].matches[0].name).toBe('WideRepeater1');
|
||||
expect(result.hops[1].prefix).toBe('3C4D');
|
||||
expect(result.hops[1].matches[0].name).toBe('WideRepeater2');
|
||||
});
|
||||
|
||||
it('handles unknown repeaters (no matches)', () => {
|
||||
const result = resolvePath('XX', sender, contacts, config);
|
||||
|
||||
@@ -545,6 +572,15 @@ describe('formatHopCounts', () => {
|
||||
expect(result.hasMultiple).toBe(false);
|
||||
});
|
||||
|
||||
it('uses explicit path_len for multi-byte hop counts', () => {
|
||||
const result = formatHopCounts([
|
||||
{ path: '1A2B3C4D', path_len: 2, received_at: 1700000000 },
|
||||
]);
|
||||
expect(result.display).toBe('2');
|
||||
expect(result.allDirect).toBe(false);
|
||||
expect(result.hasMultiple).toBe(false);
|
||||
});
|
||||
|
||||
it('formats multiple paths sorted by hop count', () => {
|
||||
const result = formatHopCounts([
|
||||
{ path: '1A2B3C', received_at: 1700000000 }, // 3 hops
|
||||
|
||||
@@ -149,10 +149,12 @@ export interface ChannelDetail {
|
||||
|
||||
/** A single path that a message took to reach us */
|
||||
export interface MessagePath {
|
||||
/** Hex-encoded routing path (2 chars per hop) */
|
||||
/** Hex-encoded routing path */
|
||||
path: string;
|
||||
/** Unix timestamp when this path was received */
|
||||
received_at: number;
|
||||
/** Number of hops in the path, when known */
|
||||
path_len?: number;
|
||||
}
|
||||
|
||||
export interface Message {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { Contact, RadioConfig, MessagePath } from '../types';
|
||||
import { CONTACT_TYPE_REPEATER } from '../types';
|
||||
|
||||
export interface PathHop {
|
||||
prefix: string; // 2-char hex prefix (e.g., "1A")
|
||||
prefix: string; // Hex prefix for a single hop
|
||||
matches: Contact[]; // Matched repeaters (empty=unknown, multiple=ambiguous)
|
||||
distanceFromPrev: number | null; // km from previous hop
|
||||
}
|
||||
@@ -30,20 +30,21 @@ export interface SenderInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* Split hex string into 2-char hops
|
||||
* Split hex string into hop-sized chunks.
|
||||
*/
|
||||
export function parsePathHops(path: string | null | undefined): string[] {
|
||||
export function parsePathHops(path: string | null | undefined, pathLen?: number): string[] {
|
||||
if (!path || path.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const normalized = path.toUpperCase();
|
||||
const hopCount = pathLen ?? Math.floor(normalized.length / 2);
|
||||
const charsPerHop =
|
||||
hopCount > 0 && normalized.length % hopCount === 0 ? normalized.length / hopCount : 2;
|
||||
const hops: string[] = [];
|
||||
|
||||
for (let i = 0; i < normalized.length; i += 2) {
|
||||
if (i + 1 < normalized.length) {
|
||||
hops.push(normalized.slice(i, i + 2));
|
||||
}
|
||||
for (let i = 0; i + charsPerHop <= normalized.length; i += charsPerHop) {
|
||||
hops.push(normalized.slice(i, i + charsPerHop));
|
||||
}
|
||||
|
||||
return hops;
|
||||
@@ -148,11 +149,11 @@ function sortContactsByDistance(
|
||||
/**
|
||||
* Get simple hop count from path string
|
||||
*/
|
||||
function getHopCount(path: string | null | undefined): number {
|
||||
function getHopCount(path: string | null | undefined, pathLen?: number): number {
|
||||
if (!path || path.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.floor(path.length / 2);
|
||||
return pathLen ?? Math.floor(path.length / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +171,7 @@ export function formatHopCounts(paths: MessagePath[] | null | undefined): {
|
||||
}
|
||||
|
||||
// Get hop counts for all paths and sort ascending
|
||||
const hopCounts = paths.map((p) => getHopCount(p.path)).sort((a, b) => a - b);
|
||||
const hopCounts = paths.map((p) => getHopCount(p.path, p.path_len)).sort((a, b) => a - b);
|
||||
|
||||
const allDirect = hopCounts.every((h) => h === 0);
|
||||
const hasMultiple = paths.length > 1;
|
||||
@@ -189,9 +190,10 @@ export function resolvePath(
|
||||
path: string | null | undefined,
|
||||
sender: SenderInfo,
|
||||
contacts: Contact[],
|
||||
config: RadioConfig | null
|
||||
config: RadioConfig | null,
|
||||
pathLen?: number
|
||||
): ResolvedPath {
|
||||
const hopPrefixes = parsePathHops(path);
|
||||
const hopPrefixes = parsePathHops(path, pathLen);
|
||||
|
||||
// Build sender info
|
||||
const senderPrefix = sender.publicKeyOrPrefix.toUpperCase().slice(0, 2);
|
||||
|
||||
Reference in New Issue
Block a user