import React from 'react'; import { bytesToHex } from '@noble/hashes/utils.js'; import { Card, Stack } from 'react-bootstrap'; import PacketDissectionViewer from '../../components/PacketDissectionViewer'; import type { Segment } from '../../types/protocol/dissection.types'; 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 }) => (
{label} {value}
); const buildMeshCoreSegments = (packet: Packet): Segment[] => { const { pathHashSize, pathHashCount } = packet; const pathLength = pathHashSize * pathHashCount; const segments: Segment[] = [ { name: 'Header', offset: 0, byteCount: 1, bitfields: [ { offset: 6, length: 2, name: 'Version', }, { offset: 2, length: 4, name: `Payload Type (${payloadNameByValue[packet.payloadType] ?? 'Unknown'})`, }, { offset: 0, length: 2, name: `Route Type (${routeDisplayByValue[packet.routeType] ?? 'Unknown'})`, }, ], }, { name: 'Path Descriptor', offset: 1, byteCount: 1, bitfields: [ { offset: 6, length: 2, name: `Hash Size Selector (size=${pathHashSize})`, }, { offset: 0, length: 6, name: `Hash Count (count=${pathHashCount})`, }, ], }, ]; if (pathLength > 0) { segments.push({ name: 'Path Data', offset: 2, byteCount: pathLength, attributes: [ { byteWidth: pathLength, type: 'bytes', name: `Path Hashes (${pathHashCount} × ${pathHashSize} bytes)`, }, ], }); } const payloadOffset = 2 + (packet.transportCodes ? packet.transportCodes.length * 2 : 0) + pathLength; if (packet.payload.length > 0) { segments.push({ name: 'Payload', offset: payloadOffset, byteCount: packet.payload.length, attributes: [ { byteWidth: packet.payload.length, type: 'bytes', name: `Payload Data (${packet.payload.length} bytes)`, }, ], }); } if (packet.segments) { segments.push(...packet.segments); } return segments; }; const asRecord = (value: unknown): Record | null => { if (value && typeof value === 'object') { return value as Record; } return null; }; const PayloadDetails: React.FC<{ packet: Packet }> = ({ packet }) => { const payload = packet.decodedPayload as Payload | undefined; const payloadObj = asRecord(payload); if (typeof payload === 'undefined' || !payload || !payloadObj) { return (
Payload
Unable to decode payload; showing raw bytes only.
{bytesToHex(packet.payload)}
); } if (typeof payloadObj?.flags === 'number' && payloadObj?.data instanceof Uint8Array) { return (
Control
{bytesToHex(payloadObj.data)}} />
); } if (packet.payloadType === PayloadType.GROUP_TEXT && typeof packet.decrypted !== 'undefined') { const payload = packet.decodedPayload as GroupTextPayload; const decrypted = packet.decrypted as DecryptedGroupMessage; return (
{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Encrypted)
{payload.channelHash}{decrypted.group ? ` (${decrypted.group})` : ''}} />
); } if ( typeof payloadObj.channelHash === 'string' && payloadObj.cipherText instanceof Uint8Array && payloadObj.cipherMAC instanceof Uint8Array ) { return (
{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Encrypted)
{bytesToHex(payloadObj.cipherMAC)}} />
); } if ( typeof payloadObj.dstHash === 'string' && typeof payloadObj.srcHash === 'string' && payloadObj.cipherText instanceof Uint8Array && payloadObj.cipherMAC instanceof Uint8Array ) { return (
{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Encrypted)
{payloadObj.dstHash}} /> {payloadObj.srcHash}} /> {bytesToHex(payloadObj.cipherMAC)}} />
); } if (payloadObj.data instanceof Uint8Array) { return (
{payloadNameByValue[packet.payloadType] ?? packet.payloadType} (Raw)
{bytesToHex(payloadObj.data)}} />
); } if (packet.payloadType === PayloadType.ADVERT) { const advert = payload as AdvertPayload; return (
Advert Payload
{bytesToHex(advert.publicKey)}} /> {advert.timestamp && ( {advert.timestamp.toISOString()}} /> )} {advert.appdata?.name && ( )} {advert.appdata?.latitude && ( )} {advert.appdata?.longitude && ( )}
); } if (packet.payloadType === PayloadType.ANON_REQ) { const anonReq = payload as AnonReqPayload; return (
Anonymous Request
{bytesToHex(anonReq.publicKey)}} />
); } return (
Payload
{JSON.stringify(payloadObj)}
); }; interface MeshCorePacketDetailsPaneProps { packet: Packet | null; streamReady: boolean; } const MeshCorePacketDetailsPane: React.FC = ({ packet, streamReady }) => { if (!packet) { return (
Select a packet
Click any hash in the table to inspect MeshCore header and payload details.
Stream prepared: {streamReady ? 'yes' : 'no'}
); } return ( {/*
{payloadNameByValue[packet.payloadType] ?? packet.payloadType}
{packet.hash}} /> {packet.snr !== undefined && } {packet.rssi !== undefined && } {packet.radioName && } {bytesToHex(packet.path)}} /> {bytesToHex(packet.raw)}} />
*/}
Stream Preparation
MeshCore stream service is initialized and ready for topic subscriptions.
Ready: {streamReady ? 'yes' : 'no'}
); }; export default MeshCorePacketDetailsPane;