Checkpoint

This commit is contained in:
2026-03-08 22:22:51 +01:00
parent 247c827291
commit 9053ec65a6
65 changed files with 5874 additions and 708 deletions

View File

@@ -1,16 +1,17 @@
import React, { useMemo } from 'react';
import React from 'react';
import { bytesToHex } from '@noble/hashes/utils.js';
import { Badge, Card, Stack } from 'react-bootstrap';
import { Card, Stack } from 'react-bootstrap';
import PacketDissectionViewer from '../../components/PacketDissectionViewer';
import type { Segment } from '../../protocols/dissection.types';
import type { Segment } from '../../types/protocol/dissection.types';
import type { MeshCorePacketRecord } from './MeshCoreContext';
import {
payloadNameByValue,
routeDisplayByValue,
} from './MeshCoreData';
import { PayloadType, type AdvertPayload, type AnonReqPayload, type DecryptedGroupMessage, type GroupTextPayload, type Payload } from '../../types/protocol/meshcore.types';
import type { Packet } from '../../protocols/meshcore';
const HeaderFact: React.FC<{ label: string; value: React.ReactNode }> = ({ label, value }) => (
<div className="meshcore-fact-row">
@@ -19,19 +20,9 @@ const HeaderFact: React.FC<{ label: string; value: React.ReactNode }> = ({ label
</div>
);
const bitSlice = (value: number, msb: number, lsb: number): number => {
const width = msb - lsb + 1;
const mask = (1 << width) - 1;
return (value >> lsb) & mask;
};
const buildMeshCoreSegments = (packet: MeshCorePacketRecord): Segment[] => {
const pathField = packet.raw.length > 1 ? packet.raw[1] : 0;
const pathHashSize = bitSlice(pathField, 7, 6) + 1;
const pathHashCount = bitSlice(pathField, 5, 0);
const pathBytesExpected = pathHashCount === 0 || pathHashSize === 4 ? 0 : pathHashCount * pathHashSize;
const pathBytesAvailable = Math.min(pathBytesExpected, Math.max(packet.raw.length - 2, 0));
const payloadOffset = 2 + pathBytesAvailable;
const buildMeshCoreSegments = (packet: Packet): Segment[] => {
const { pathHashSize, pathHashCount } = packet;
const pathLength = pathHashSize * pathHashCount;
const segments: Segment[] = [
{
@@ -75,14 +66,14 @@ const buildMeshCoreSegments = (packet: MeshCorePacketRecord): Segment[] => {
},
];
if (pathBytesAvailable > 0) {
if (pathLength > 0) {
segments.push({
name: 'Path Data',
offset: 2,
byteCount: pathBytesAvailable,
byteCount: pathLength,
attributes: [
{
byteWidth: pathBytesAvailable,
byteWidth: pathLength,
type: 'bytes',
name: `Path Hashes (${pathHashCount} × ${pathHashSize} bytes)`,
},
@@ -90,22 +81,26 @@ const buildMeshCoreSegments = (packet: MeshCorePacketRecord): Segment[] => {
});
}
const payloadLength = packet.raw.length - payloadOffset;
if (payloadLength > 0) {
const payloadOffset = 2 + (packet.transportCodes ? packet.transportCodes.length * 2 : 0) + pathLength;
if (packet.payload.length > 0) {
segments.push({
name: 'Payload',
offset: payloadOffset,
byteCount: payloadLength,
byteCount: packet.payload.length,
attributes: [
{
byteWidth: payloadLength,
byteWidth: packet.payload.length,
type: 'bytes',
name: `Payload Data (${payloadLength} bytes)`,
name: `Payload Data (${packet.payload.length} bytes)`,
},
],
});
}
if (packet.segments) {
segments.push(...packet.segments);
}
return segments;
};
@@ -116,24 +111,24 @@ const asRecord = (value: unknown): Record<string, unknown> | null => {
return null;
};
const PayloadDetails: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet }) => {
const payload = packet.decodedPayload;
const PayloadDetails: React.FC<{ packet: Packet }> = ({ packet }) => {
const payload = packet.decodedPayload as Payload | undefined;
const payloadObj = asRecord(payload);
if (!payloadObj) {
if (typeof payload === 'undefined' || !payload || !payloadObj) {
return (
<Card body className="data-table-card">
<h6 className="mb-2">Payload</h6>
<div>Unable to decode payload; showing raw bytes only.</div>
<code>{bytesToHex(packet.raw)}</code>
<code>{bytesToHex(packet.payload)}</code>
</Card>
);
}
if (typeof payloadObj.flags === 'number' && payloadObj.data instanceof Uint8Array) {
if (typeof payloadObj?.flags === 'number' && payloadObj?.data instanceof Uint8Array) {
return (
<Card body className="data-table-card">
<h6 className="mb-2">CONTROL Payload</h6>
<h6 className="mb-2">Control</h6>
<HeaderFact label="Flags" value={`0x${payloadObj.flags.toString(16)}`} />
<HeaderFact label="Data Length" value={payloadObj.data.length} />
<HeaderFact label="Data" value={<code>{bytesToHex(payloadObj.data)}</code>} />
@@ -141,6 +136,18 @@ const PayloadDetails: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet })
);
}
if (packet.payloadType === PayloadType.GROUP_TEXT && typeof packet.decrypted !== 'undefined') {
const payload = packet.decodedPayload as GroupTextPayload;
const decrypted = packet.decrypted as DecryptedGroupMessage;
return (
<Card body className="data-table-card">
<h6 className="mb-2">{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Encrypted)</h6>
<HeaderFact label="Channel Hash" value={<><span className="meshcore-hash">{payload.channelHash}</span>{decrypted.group ? ` (${decrypted.group})` : ''}</>} />
<HeaderFact label="Timestamp" value={decrypted.timestamp.toLocaleString()} />
<HeaderFact label="Message" value={decrypted.message} />
</Card>
);
}
if (
typeof payloadObj.channelHash === 'string'
&& payloadObj.cipherText instanceof Uint8Array
@@ -148,9 +155,9 @@ const PayloadDetails: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet })
) {
return (
<Card body className="data-table-card">
<h6 className="mb-2">GROUP Payload</h6>
<h6 className="mb-2">{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Encrypted)</h6>
<HeaderFact label="Channel Hash" value={payloadObj.channelHash} />
<HeaderFact label="Cipher Text Length" value={payloadObj.cipherText.length} />
<HeaderFact label="Cipher Text" value={payloadObj.cipherText.length + ' bytes'} />
<HeaderFact label="Cipher MAC" value={<code>{bytesToHex(payloadObj.cipherMAC)}</code>} />
</Card>
);
@@ -164,9 +171,9 @@ const PayloadDetails: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet })
) {
return (
<Card body className="data-table-card">
<h6 className="mb-2">Encrypted Payload</h6>
<HeaderFact label="Destination" value={payloadObj.dstHash} />
<HeaderFact label="Source" value={payloadObj.srcHash} />
<h6 className="mb-2">{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Encrypted)</h6>
<HeaderFact label="Destination" value={<span className="meshcore-hash">{payloadObj.dstHash}</span>} />
<HeaderFact label="Source" value={<span className="meshcore-hash">{payloadObj.srcHash}</span>} />
<HeaderFact label="Cipher Text Length" value={payloadObj.cipherText.length} />
<HeaderFact label="Cipher MAC" value={<code>{bytesToHex(payloadObj.cipherMAC)}</code>} />
</Card>
@@ -176,13 +183,47 @@ const PayloadDetails: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet })
if (payloadObj.data instanceof Uint8Array) {
return (
<Card body className="data-table-card">
<h6 className="mb-2">Raw Payload</h6>
<h6 className="mb-2">{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Raw)</h6>
<HeaderFact label="Data Length" value={payloadObj.data.length} />
<HeaderFact label="Data" value={<code>{bytesToHex(payloadObj.data)}</code>} />
</Card>
);
}
if (packet.payloadType === PayloadType.ADVERT) {
const advert = payload as AdvertPayload;
return (
<Card body className="data-table-card">
<h6 className="mb-2">Advert Payload</h6>
<HeaderFact label="Public Key" value={<code>{bytesToHex(advert.publicKey)}</code>} />
<HeaderFact label="Signature" value={advert.signature.length + ' Bytes'} />
{advert.timestamp && (
<HeaderFact label="Time" value={<code>{advert.timestamp.toISOString()}</code>} />
)}
{advert.appdata?.name && (
<HeaderFact label="Name" value={advert.appdata.name} />
)}
{advert.appdata?.latitude && (
<HeaderFact label="Latitude" value={advert.appdata.latitude} />
)}
{advert.appdata?.longitude && (
<HeaderFact label="Longitude" value={advert.appdata.longitude} />
)}
</Card>
);
}
if (packet.payloadType === PayloadType.ANON_REQ) {
const anonReq = payload as AnonReqPayload;
return (
<Card body className="data-table-card">
<h6 className="mb-2">Anonymous Request</h6>
<HeaderFact label="Destination" value={anonReq.dstHash} />
<HeaderFact label="Public Key" value={<code>{bytesToHex(anonReq.publicKey)}</code>} />
</Card>
);
}
return (
<Card body className="data-table-card">
<h6 className="mb-2">Payload</h6>
@@ -192,7 +233,7 @@ const PayloadDetails: React.FC<{ packet: MeshCorePacketRecord }> = ({ packet })
};
interface MeshCorePacketDetailsPaneProps {
packet: MeshCorePacketRecord | null;
packet: Packet | null;
streamReady: boolean;
}
@@ -209,6 +250,7 @@ const MeshCorePacketDetailsPane: React.FC<MeshCorePacketDetailsPaneProps> = ({ p
return (
<Stack gap={2} className="h-100 meshcore-detail-stack">
{/*
<Card body className="data-table-card">
<Stack direction="horizontal" gap={2} className="mb-2">
<h6 className="mb-0">{payloadNameByValue[packet.payloadType] ?? packet.payloadType}</h6>
@@ -225,14 +267,15 @@ const MeshCorePacketDetailsPane: React.FC<MeshCorePacketDetailsPaneProps> = ({ p
<HeaderFact label="Path" value={<code>{bytesToHex(packet.path)}</code>} />
<HeaderFact label="Raw Packet" value={<code>{bytesToHex(packet.raw)}</code>} />
</Card>
*/}
<PayloadDetails packet={packet} />
<Card body className="data-table-card">
<PacketDissectionViewer
rawPacket={packet.raw}
segments={buildMeshCoreSegments(packet)}
title="Packet Bytes (Wire View)"
rawPacket={packet.toBytes()}
segments={packet.segments || buildMeshCoreSegments(packet)}
title={`${payloadNameByValue[packet.payloadType] ?? packet.payloadType} Packet Dissection`}
/>
</Card>
<PayloadDetails packet={packet} />
<Card body className="data-table-card">
<h6 className="mb-2">Stream Preparation</h6>
<div>MeshCore stream service is initialized and ready for topic subscriptions.</div>