Fix multibyte meshcore-decoder dep hell

This commit is contained in:
Jack Kingsman
2026-03-08 12:34:47 -07:00
parent 7ac220aee1
commit f472ff7cab
109 changed files with 4841 additions and 8 deletions
+3
View File
@@ -0,0 +1,3 @@
#!/usr/bin/env node
export {};
//# sourceMappingURL=cli.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
+409
View File
@@ -0,0 +1,409 @@
#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const packet_decoder_1 = require("./decoder/packet-decoder");
const enums_1 = require("./types/enums");
const enum_names_1 = require("./utils/enum-names");
const index_1 = require("./index");
const commander_1 = require("commander");
const chalk_1 = __importDefault(require("chalk"));
const packageJson = __importStar(require("../package.json"));
commander_1.program
.name('meshcore-decoder')
.description('CLI tool for decoding MeshCore packets')
.version(packageJson.version);
// Default decode command
commander_1.program
.command('decode', { isDefault: true })
.description('Decode a MeshCore packet')
.argument('<hex>', 'Hex string of the packet to decode')
.option('-k, --key <keys...>', 'Channel secret keys for decryption (hex)')
.option('-j, --json', 'Output as JSON instead of formatted text')
.option('-s, --structure', 'Show detailed packet structure analysis')
.action(async (hex, options) => {
try {
// Clean up hex input
const cleanHex = hex.replace(/\s+/g, '').replace(/^0x/i, '');
// Create key store if keys provided
let keyStore;
if (options.key && options.key.length > 0) {
keyStore = packet_decoder_1.MeshCorePacketDecoder.createKeyStore({
channelSecrets: options.key
});
}
// Decode packet with signature verification
const packet = await packet_decoder_1.MeshCorePacketDecoder.decodeWithVerification(cleanHex, { keyStore });
if (options.json) {
// JSON output
if (options.structure) {
const structure = await packet_decoder_1.MeshCorePacketDecoder.analyzeStructureWithVerification(cleanHex, { keyStore });
console.log(JSON.stringify({ packet, structure }, null, 2));
}
else {
console.log(JSON.stringify(packet, null, 2));
}
}
else {
// Formatted output
console.log(chalk_1.default.cyan('=== MeshCore Packet Analysis ===\n'));
if (!packet.isValid) {
console.log(chalk_1.default.red('❌ Invalid Packet'));
if (packet.errors) {
packet.errors.forEach(error => console.log(chalk_1.default.red(` ${error}`)));
}
}
else {
console.log(chalk_1.default.green('✅ Valid Packet'));
}
console.log(`${chalk_1.default.bold('Message Hash:')} ${packet.messageHash}`);
console.log(`${chalk_1.default.bold('Route Type:')} ${(0, enum_names_1.getRouteTypeName)(packet.routeType)}`);
console.log(`${chalk_1.default.bold('Payload Type:')} ${(0, enum_names_1.getPayloadTypeName)(packet.payloadType)}`);
console.log(`${chalk_1.default.bold('Total Bytes:')} ${packet.totalBytes}`);
if (packet.path && packet.path.length > 0) {
console.log(`${chalk_1.default.bold('Path:')} ${packet.path.join(' → ')}`);
}
// Show payload details (even for invalid packets)
if (packet.payload.decoded) {
console.log(chalk_1.default.cyan('\n=== Payload Details ==='));
showPayloadDetails(packet.payload.decoded);
}
// Exit with error code if packet is invalid
if (!packet.isValid) {
process.exit(1);
}
// Show structure if requested
if (options.structure) {
const structure = await packet_decoder_1.MeshCorePacketDecoder.analyzeStructureWithVerification(cleanHex, { keyStore });
console.log(chalk_1.default.cyan('\n=== Packet Structure ==='));
console.log(chalk_1.default.yellow('\nMain Segments:'));
structure.segments.forEach((seg, i) => {
console.log(`${i + 1}. ${chalk_1.default.bold(seg.name)} (bytes ${seg.startByte}-${seg.endByte}): ${seg.value}`);
if (seg.description) {
console.log(` ${chalk_1.default.dim(seg.description)}`);
}
});
if (structure.payload.segments.length > 0) {
console.log(chalk_1.default.yellow('\nPayload Segments:'));
structure.payload.segments.forEach((seg, i) => {
console.log(`${i + 1}. ${chalk_1.default.bold(seg.name)} (bytes ${seg.startByte}-${seg.endByte}): ${seg.value}`);
console.log(` ${chalk_1.default.dim(seg.description)}`);
});
}
}
}
}
catch (error) {
console.error(chalk_1.default.red('Error:'), error.message);
process.exit(1);
}
});
function showPayloadDetails(payload) {
switch (payload.type) {
case enums_1.PayloadType.Advert:
const advert = payload;
console.log(`${chalk_1.default.bold('Device Role:')} ${(0, enum_names_1.getDeviceRoleName)(advert.appData.deviceRole)}`);
if (advert.appData.name) {
console.log(`${chalk_1.default.bold('Device Name:')} ${advert.appData.name}`);
}
if (advert.appData.location) {
console.log(`${chalk_1.default.bold('Location:')} ${advert.appData.location.latitude}, ${advert.appData.location.longitude}`);
}
console.log(`${chalk_1.default.bold('Timestamp:')} ${new Date(advert.timestamp * 1000).toISOString()}`);
// Show signature verification status
if (advert.signatureValid !== undefined) {
if (advert.signatureValid) {
console.log(`${chalk_1.default.bold('Signature:')} ${chalk_1.default.green('✅ Valid Ed25519 signature')}`);
}
else {
console.log(`${chalk_1.default.bold('Signature:')} ${chalk_1.default.red('❌ Invalid Ed25519 signature')}`);
if (advert.signatureError) {
console.log(`${chalk_1.default.bold('Error:')} ${chalk_1.default.red(advert.signatureError)}`);
}
}
}
else {
console.log(`${chalk_1.default.bold('Signature:')} ${chalk_1.default.yellow('⚠️ Not verified (use async verification)')}`);
}
break;
case enums_1.PayloadType.GroupText:
const groupText = payload;
console.log(`${chalk_1.default.bold('Channel Hash:')} ${groupText.channelHash}`);
if (groupText.decrypted) {
console.log(chalk_1.default.green('🔓 Decrypted Message:'));
if (groupText.decrypted.sender) {
console.log(`${chalk_1.default.bold('Sender:')} ${groupText.decrypted.sender}`);
}
console.log(`${chalk_1.default.bold('Message:')} ${groupText.decrypted.message}`);
console.log(`${chalk_1.default.bold('Timestamp:')} ${new Date(groupText.decrypted.timestamp * 1000).toISOString()}`);
}
else {
console.log(chalk_1.default.yellow('🔒 Encrypted (no key available)'));
console.log(`${chalk_1.default.bold('Ciphertext:')} ${groupText.ciphertext.substring(0, 32)}...`);
}
break;
case enums_1.PayloadType.Trace:
const trace = payload;
console.log(`${chalk_1.default.bold('Trace Tag:')} ${trace.traceTag}`);
console.log(`${chalk_1.default.bold('Auth Code:')} ${trace.authCode}`);
if (trace.snrValues && trace.snrValues.length > 0) {
console.log(`${chalk_1.default.bold('SNR Values:')} ${trace.snrValues.map(snr => `${snr.toFixed(1)}dB`).join(', ')}`);
}
break;
default:
console.log(`${chalk_1.default.bold('Type:')} ${(0, enum_names_1.getPayloadTypeName)(payload.type)}`);
console.log(`${chalk_1.default.bold('Valid:')} ${payload.isValid ? '✅' : '❌'}`);
}
}
// Add key derivation command
commander_1.program
.command('derive-key')
.description('Derive Ed25519 public key from MeshCore private key')
.argument('<private-key>', '64-byte private key in hex format')
.option('-v, --validate <public-key>', 'Validate against expected public key')
.option('-j, --json', 'Output as JSON')
.action(async (privateKeyHex, options) => {
try {
// Clean up hex input
const cleanPrivateKey = privateKeyHex.replace(/\s+/g, '').replace(/^0x/i, '');
if (cleanPrivateKey.length !== 128) {
console.error(chalk_1.default.red('❌ Error: Private key must be exactly 64 bytes (128 hex characters)'));
process.exit(1);
}
if (options.json) {
// JSON output
const result = {
privateKey: cleanPrivateKey,
derivedPublicKey: await index_1.Utils.derivePublicKey(cleanPrivateKey)
};
if (options.validate) {
const cleanExpectedKey = options.validate.replace(/\s+/g, '').replace(/^0x/i, '');
result.expectedPublicKey = cleanExpectedKey;
result.isValid = await index_1.Utils.validateKeyPair(cleanPrivateKey, cleanExpectedKey);
result.match = result.derivedPublicKey.toLowerCase() === cleanExpectedKey.toLowerCase();
}
console.log(JSON.stringify(result, null, 2));
}
else {
// Formatted output
console.log(chalk_1.default.cyan('=== MeshCore Ed25519 Key Derivation ===\n'));
console.log(chalk_1.default.bold('Private Key (64 bytes):'));
console.log(chalk_1.default.gray(cleanPrivateKey));
console.log();
console.log(chalk_1.default.bold('Derived Public Key (32 bytes):'));
const derivedKey = await index_1.Utils.derivePublicKey(cleanPrivateKey);
console.log(chalk_1.default.green(derivedKey));
console.log();
if (options.validate) {
const cleanExpectedKey = options.validate.replace(/\s+/g, '').replace(/^0x/i, '');
console.log(chalk_1.default.bold('Expected Public Key:'));
console.log(chalk_1.default.gray(cleanExpectedKey));
console.log();
const match = derivedKey.toLowerCase() === cleanExpectedKey.toLowerCase();
console.log(chalk_1.default.bold('Validation:'));
console.log(match ? chalk_1.default.green('Keys match') : chalk_1.default.red('Keys do not match'));
if (!match) {
process.exit(1);
}
}
console.log(chalk_1.default.green('Key derivation completed successfully'));
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (options.json) {
console.log(JSON.stringify({ error: errorMessage }, null, 2));
}
else {
console.error(chalk_1.default.red(`Error: ${errorMessage}`));
}
process.exit(1);
}
});
// Add auth-token command
commander_1.program
.command('auth-token')
.description('Generate JWT authentication token signed with Ed25519 private key')
.argument('<public-key>', '32-byte public key in hex format')
.argument('<private-key>', '64-byte private key in hex format')
.option('-e, --exp <seconds>', 'Token expiration in seconds from now (default: 86400 = 24 hours)', '86400')
.option('-c, --claims <json>', 'Additional claims as JSON object (e.g., \'{"aud":"mqtt.example.com","sub":"device-123"}\')')
.option('-j, --json', 'Output as JSON')
.action(async (publicKeyHex, privateKeyHex, options) => {
try {
const { createAuthToken } = await Promise.resolve().then(() => __importStar(require('./utils/auth-token')));
// Clean up hex inputs
const cleanPublicKey = publicKeyHex.replace(/\s+/g, '').replace(/^0x/i, '');
const cleanPrivateKey = privateKeyHex.replace(/\s+/g, '').replace(/^0x/i, '');
if (cleanPublicKey.length !== 64) {
console.error(chalk_1.default.red('❌ Error: Public key must be exactly 32 bytes (64 hex characters)'));
process.exit(1);
}
if (cleanPrivateKey.length !== 128) {
console.error(chalk_1.default.red('❌ Error: Private key must be exactly 64 bytes (128 hex characters)'));
process.exit(1);
}
const expSeconds = parseInt(options.exp);
const iat = Math.floor(Date.now() / 1000);
const exp = iat + expSeconds;
const payload = {
publicKey: cleanPublicKey.toUpperCase(),
iat,
exp
};
// Parse and merge additional claims if provided
if (options.claims) {
try {
const additionalClaims = JSON.parse(options.claims);
Object.assign(payload, additionalClaims);
}
catch (e) {
console.error(chalk_1.default.red('❌ Error: Invalid JSON in --claims option'));
process.exit(1);
}
}
const token = await createAuthToken(payload, cleanPrivateKey, cleanPublicKey.toUpperCase());
if (options.json) {
console.log(JSON.stringify({
token,
payload
}, null, 2));
}
else {
console.log(token);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (options.json) {
console.log(JSON.stringify({ error: errorMessage }, null, 2));
}
else {
console.error(chalk_1.default.red(`Error: ${errorMessage}`));
}
process.exit(1);
}
});
// Add verify-token command
commander_1.program
.command('verify-token')
.description('Verify JWT authentication token')
.argument('<token>', 'JWT token to verify')
.option('-p, --public-key <key>', 'Expected public key in hex format (optional)')
.option('-j, --json', 'Output as JSON')
.action(async (token, options) => {
try {
const { verifyAuthToken } = await Promise.resolve().then(() => __importStar(require('./utils/auth-token')));
const cleanToken = token.trim();
let expectedPublicKey;
if (options.publicKey) {
const cleanKey = options.publicKey.replace(/\s+/g, '').replace(/^0x/i, '').toUpperCase();
if (cleanKey.length !== 64) {
console.error(chalk_1.default.red('❌ Error: Public key must be exactly 32 bytes (64 hex characters)'));
process.exit(1);
}
expectedPublicKey = cleanKey;
}
const payload = await verifyAuthToken(cleanToken, expectedPublicKey);
if (payload) {
const now = Math.floor(Date.now() / 1000);
const isExpired = payload.exp && now > payload.exp;
const timeToExpiry = payload.exp ? payload.exp - now : null;
if (options.json) {
console.log(JSON.stringify({
valid: true,
expired: isExpired,
payload,
timeToExpiry
}, null, 2));
}
else {
console.log(chalk_1.default.green('✅ Token is valid'));
console.log(chalk_1.default.cyan('\nPayload:'));
console.log(` Public Key: ${payload.publicKey}`);
console.log(` Issued At: ${new Date(payload.iat * 1000).toISOString()} (${payload.iat})`);
if (payload.exp) {
console.log(` Expires At: ${new Date(payload.exp * 1000).toISOString()} (${payload.exp})`);
if (isExpired) {
console.log(chalk_1.default.red(` Status: EXPIRED`));
}
else {
console.log(chalk_1.default.green(` Status: Valid for ${timeToExpiry} more seconds`));
}
}
// Show any additional claims
const standardClaims = ['publicKey', 'iat', 'exp'];
const customClaims = Object.keys(payload).filter(k => !standardClaims.includes(k));
if (customClaims.length > 0) {
console.log(chalk_1.default.cyan('\nCustom Claims:'));
customClaims.forEach(key => {
console.log(` ${key}: ${JSON.stringify(payload[key])}`);
});
}
}
}
else {
if (options.json) {
console.log(JSON.stringify({
valid: false,
error: 'Token verification failed'
}, null, 2));
}
else {
console.error(chalk_1.default.red('❌ Token verification failed'));
console.error(chalk_1.default.yellow('Possible reasons:'));
console.error(' - Invalid signature');
console.error(' - Token format is incorrect');
console.error(' - Public key mismatch (if --public-key was provided)');
}
process.exit(1);
}
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
if (options.json) {
console.log(JSON.stringify({ valid: false, error: errorMessage }, null, 2));
}
else {
console.error(chalk_1.default.red(`Error: ${errorMessage}`));
}
process.exit(1);
}
});
commander_1.program.parse();
//# sourceMappingURL=cli.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,15 @@
import { DecryptionResult } from '../types/crypto';
export declare class ChannelCrypto {
/**
* Decrypt GroupText message using MeshCore algorithm:
* - HMAC-SHA256 verification with 2-byte MAC
* - AES-128 ECB decryption
*/
static decryptGroupTextMessage(ciphertext: string, cipherMac: string, channelKey: string): DecryptionResult;
/**
* Calculate MeshCore channel hash from secret key
* Returns the first byte of SHA256(secret) as hex string
*/
static calculateChannelHash(secretKeyHex: string): string;
}
//# sourceMappingURL=channel-crypto.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"channel-crypto.d.ts","sourceRoot":"","sources":["../../src/crypto/channel-crypto.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,qBAAa,aAAa;IACxB;;;;OAIG;IACH,MAAM,CAAC,uBAAuB,CAC5B,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,gBAAgB;IAuFnB;;;OAGG;IACH,MAAM,CAAC,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM;CAK1D"}
@@ -0,0 +1,94 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChannelCrypto = void 0;
const crypto_js_1 = require("crypto-js");
const hex_1 = require("../utils/hex");
class ChannelCrypto {
/**
* Decrypt GroupText message using MeshCore algorithm:
* - HMAC-SHA256 verification with 2-byte MAC
* - AES-128 ECB decryption
*/
static decryptGroupTextMessage(ciphertext, cipherMac, channelKey) {
try {
// convert hex strings to byte arrays
const channelKey16 = (0, hex_1.hexToBytes)(channelKey);
const macBytes = (0, hex_1.hexToBytes)(cipherMac);
// MeshCore uses 32-byte channel secret: 16-byte key + 16 zero bytes
const channelSecret = new Uint8Array(32);
channelSecret.set(channelKey16, 0);
// Step 1: Verify HMAC-SHA256 using full 32-byte channel secret
const calculatedMac = (0, crypto_js_1.HmacSHA256)(crypto_js_1.enc.Hex.parse(ciphertext), crypto_js_1.enc.Hex.parse((0, hex_1.bytesToHex)(channelSecret)));
const calculatedMacBytes = (0, hex_1.hexToBytes)(calculatedMac.toString(crypto_js_1.enc.Hex));
const calculatedMacFirst2 = calculatedMacBytes.slice(0, 2);
if (calculatedMacFirst2[0] !== macBytes[0] || calculatedMacFirst2[1] !== macBytes[1]) {
return { success: false, error: 'MAC verification failed' };
}
// Step 2: Decrypt using AES-128 ECB with first 16 bytes of channel secret
const keyWords = crypto_js_1.enc.Hex.parse(channelKey);
const ciphertextWords = crypto_js_1.enc.Hex.parse(ciphertext);
const decrypted = crypto_js_1.AES.decrypt(crypto_js_1.lib.CipherParams.create({ ciphertext: ciphertextWords }), keyWords, { mode: crypto_js_1.mode.ECB, padding: crypto_js_1.pad.NoPadding });
const decryptedBytes = (0, hex_1.hexToBytes)(decrypted.toString(crypto_js_1.enc.Hex));
if (!decryptedBytes || decryptedBytes.length < 5) {
return { success: false, error: 'Decrypted content too short' };
}
// parse MeshCore format: timestamp(4) + flags(1) + message_text
const timestamp = decryptedBytes[0] |
(decryptedBytes[1] << 8) |
(decryptedBytes[2] << 16) |
(decryptedBytes[3] << 24);
const flagsAndAttempt = decryptedBytes[4];
// extract message text with UTF-8 decoding
const messageBytes = decryptedBytes.slice(5);
const decoder = new TextDecoder('utf-8');
let messageText = decoder.decode(messageBytes);
// remove null terminator if present
const nullIndex = messageText.indexOf('\0');
if (nullIndex >= 0) {
messageText = messageText.substring(0, nullIndex);
}
// parse sender and message (format: "sender: message")
const colonIndex = messageText.indexOf(': ');
let sender;
let content;
if (colonIndex > 0 && colonIndex < 50) {
const potentialSender = messageText.substring(0, colonIndex);
if (!/[:\[\]]/.test(potentialSender)) {
sender = potentialSender;
content = messageText.substring(colonIndex + 2);
}
else {
content = messageText;
}
}
else {
content = messageText;
}
return {
success: true,
data: {
timestamp,
flags: flagsAndAttempt,
sender,
message: content
}
};
}
catch (error) {
return { success: false, error: error instanceof Error ? error.message : 'Decryption failed' };
}
}
/**
* Calculate MeshCore channel hash from secret key
* Returns the first byte of SHA256(secret) as hex string
*/
static calculateChannelHash(secretKeyHex) {
const hash = (0, crypto_js_1.SHA256)(crypto_js_1.enc.Hex.parse(secretKeyHex));
const hashBytes = (0, hex_1.hexToBytes)(hash.toString(crypto_js_1.enc.Hex));
return hashBytes[0].toString(16).padStart(2, '0');
}
}
exports.ChannelCrypto = ChannelCrypto;
//# sourceMappingURL=channel-crypto.js.map
@@ -0,0 +1 @@
{"version":3,"file":"channel-crypto.js","sourceRoot":"","sources":["../../src/crypto/channel-crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
export declare class Ed25519SignatureVerifier {
/**
* Verify an Ed25519 signature for MeshCore advertisement packets
*
* According to MeshCore protocol, the signed message for advertisements is:
* timestamp (4 bytes LE) + flags (1 byte) + location (8 bytes LE, if present) + name (variable, if present)
*/
static verifyAdvertisementSignature(publicKeyHex: string, signatureHex: string, timestamp: number, appDataHex: string): Promise<boolean>;
/**
* Construct the signed message for MeshCore advertisements
* According to MeshCore source (Mesh.cpp lines 242-248):
* Format: public_key (32 bytes) + timestamp (4 bytes LE) + app_data (variable length)
*/
private static constructAdvertSignedMessage;
/**
* Get a human-readable description of what was signed
*/
static getSignedMessageDescription(publicKeyHex: string, timestamp: number, appDataHex: string): string;
/**
* Get the hex representation of the signed message for debugging
*/
static getSignedMessageHex(publicKeyHex: string, timestamp: number, appDataHex: string): string;
/**
* Derive Ed25519 public key from orlp/ed25519 private key format
* This implements the same algorithm as orlp/ed25519's ed25519_derive_pub()
*
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @returns 32-byte public key in hex format
*/
static derivePublicKey(privateKeyHex: string): Promise<string>;
/**
* Derive Ed25519 public key from orlp/ed25519 private key format (synchronous version)
* This implements the same algorithm as orlp/ed25519's ed25519_derive_pub()
*
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @returns 32-byte public key in hex format
*/
static derivePublicKeySync(privateKeyHex: string): string;
/**
* Validate that a private key correctly derives to the expected public key
*
* @param privateKeyHex - 64-byte private key in hex format
* @param expectedPublicKeyHex - Expected 32-byte public key in hex format
* @returns true if the private key derives to the expected public key
*/
static validateKeyPair(privateKeyHex: string, expectedPublicKeyHex: string): Promise<boolean>;
}
//# sourceMappingURL=ed25519-verifier.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ed25519-verifier.d.ts","sourceRoot":"","sources":["../../src/crypto/ed25519-verifier.ts"],"names":[],"mappings":"AAyEA,qBAAa,wBAAwB;IACnC;;;;;OAKG;WACU,4BAA4B,CACvC,YAAY,EAAE,MAAM,EACpB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC;IAkBnB;;;;OAIG;IACH,OAAO,CAAC,MAAM,CAAC,4BAA4B;IAuB3C;;OAEG;IACH,MAAM,CAAC,2BAA2B,CAChC,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM;IAIT;;OAEG;IACH,MAAM,CAAC,mBAAmB,CACxB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM;IAMT;;;;;;OAMG;WACU,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAepE;;;;;;OAMG;IACH,MAAM,CAAC,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM;IAezD;;;;;;OAMG;WACU,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAQpG"}
@@ -0,0 +1,217 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Ed25519SignatureVerifier = void 0;
const ed25519 = __importStar(require("@noble/ed25519"));
const hex_1 = require("../utils/hex");
const orlp_ed25519_wasm_1 = require("./orlp-ed25519-wasm");
// Cross-platform SHA-512 implementation
async function sha512Hash(data) {
// Browser environment - use Web Crypto API
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle) {
const hashBuffer = await globalThis.crypto.subtle.digest('SHA-512', data);
return new Uint8Array(hashBuffer);
}
// Node.js environment - use crypto module
if (typeof require !== 'undefined') {
try {
const { createHash } = require('crypto');
return createHash('sha512').update(data).digest();
}
catch (error) {
// Fallback for environments where require is not available
}
}
throw new Error('No SHA-512 implementation available');
}
function sha512HashSync(data) {
// Node.js environment - use crypto module
if (typeof require !== 'undefined') {
try {
const { createHash } = require('crypto');
return createHash('sha512').update(data).digest();
}
catch (error) {
// Fallback
}
}
// Browser environment fallback - use crypto-js for sync operation
try {
const CryptoJS = require('crypto-js');
const wordArray = CryptoJS.lib.WordArray.create(data);
const hash = CryptoJS.SHA512(wordArray);
const hashBytes = new Uint8Array(64);
// Convert CryptoJS hash to Uint8Array
for (let i = 0; i < 16; i++) {
const word = hash.words[i] || 0;
hashBytes[i * 4] = (word >>> 24) & 0xff;
hashBytes[i * 4 + 1] = (word >>> 16) & 0xff;
hashBytes[i * 4 + 2] = (word >>> 8) & 0xff;
hashBytes[i * 4 + 3] = word & 0xff;
}
return hashBytes;
}
catch (error) {
// Final fallback - this should not happen since crypto-js is a dependency
throw new Error('No SHA-512 implementation available for synchronous operation');
}
}
// Set up SHA-512 for @noble/ed25519
ed25519.etc.sha512Async = sha512Hash;
// Always set up sync version - @noble/ed25519 requires it
// It will throw in browser environments, which @noble/ed25519 can handle
try {
ed25519.etc.sha512Sync = sha512HashSync;
}
catch (error) {
console.debug('Could not set up synchronous SHA-512:', error);
}
class Ed25519SignatureVerifier {
/**
* Verify an Ed25519 signature for MeshCore advertisement packets
*
* According to MeshCore protocol, the signed message for advertisements is:
* timestamp (4 bytes LE) + flags (1 byte) + location (8 bytes LE, if present) + name (variable, if present)
*/
static async verifyAdvertisementSignature(publicKeyHex, signatureHex, timestamp, appDataHex) {
try {
// Convert hex strings to Uint8Arrays
const publicKey = (0, hex_1.hexToBytes)(publicKeyHex);
const signature = (0, hex_1.hexToBytes)(signatureHex);
const appData = (0, hex_1.hexToBytes)(appDataHex);
// Construct the signed message according to MeshCore format
const message = this.constructAdvertSignedMessage(publicKeyHex, timestamp, appData);
// Verify the signature using noble-ed25519
return await ed25519.verify(signature, message, publicKey);
}
catch (error) {
console.error('Ed25519 signature verification failed:', error);
return false;
}
}
/**
* Construct the signed message for MeshCore advertisements
* According to MeshCore source (Mesh.cpp lines 242-248):
* Format: public_key (32 bytes) + timestamp (4 bytes LE) + app_data (variable length)
*/
static constructAdvertSignedMessage(publicKeyHex, timestamp, appData) {
const publicKey = (0, hex_1.hexToBytes)(publicKeyHex);
// Timestamp (4 bytes, little-endian)
const timestampBytes = new Uint8Array(4);
timestampBytes[0] = timestamp & 0xFF;
timestampBytes[1] = (timestamp >> 8) & 0xFF;
timestampBytes[2] = (timestamp >> 16) & 0xFF;
timestampBytes[3] = (timestamp >> 24) & 0xFF;
// Concatenate: public_key + timestamp + app_data
const message = new Uint8Array(32 + 4 + appData.length);
message.set(publicKey, 0);
message.set(timestampBytes, 32);
message.set(appData, 36);
return message;
}
/**
* Get a human-readable description of what was signed
*/
static getSignedMessageDescription(publicKeyHex, timestamp, appDataHex) {
return `Public Key: ${publicKeyHex} + Timestamp: ${timestamp} (${new Date(timestamp * 1000).toISOString()}) + App Data: ${appDataHex}`;
}
/**
* Get the hex representation of the signed message for debugging
*/
static getSignedMessageHex(publicKeyHex, timestamp, appDataHex) {
const appData = (0, hex_1.hexToBytes)(appDataHex);
const message = this.constructAdvertSignedMessage(publicKeyHex, timestamp, appData);
return (0, hex_1.bytesToHex)(message);
}
/**
* Derive Ed25519 public key from orlp/ed25519 private key format
* This implements the same algorithm as orlp/ed25519's ed25519_derive_pub()
*
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @returns 32-byte public key in hex format
*/
static async derivePublicKey(privateKeyHex) {
try {
const privateKeyBytes = (0, hex_1.hexToBytes)(privateKeyHex);
if (privateKeyBytes.length !== 64) {
throw new Error(`Invalid private key length: expected 64 bytes, got ${privateKeyBytes.length}`);
}
// Use the orlp/ed25519 WebAssembly implementation
return await (0, orlp_ed25519_wasm_1.derivePublicKey)(privateKeyHex);
}
catch (error) {
throw new Error(`Failed to derive public key: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Derive Ed25519 public key from orlp/ed25519 private key format (synchronous version)
* This implements the same algorithm as orlp/ed25519's ed25519_derive_pub()
*
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @returns 32-byte public key in hex format
*/
static derivePublicKeySync(privateKeyHex) {
try {
const privateKeyBytes = (0, hex_1.hexToBytes)(privateKeyHex);
if (privateKeyBytes.length !== 64) {
throw new Error(`Invalid private key length: expected 64 bytes, got ${privateKeyBytes.length}`);
}
// Note: WASM operations are async, so this sync version throws an error
throw new Error('Synchronous key derivation not supported with WASM. Use derivePublicKey() instead.');
}
catch (error) {
throw new Error(`Failed to derive public key: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Validate that a private key correctly derives to the expected public key
*
* @param privateKeyHex - 64-byte private key in hex format
* @param expectedPublicKeyHex - Expected 32-byte public key in hex format
* @returns true if the private key derives to the expected public key
*/
static async validateKeyPair(privateKeyHex, expectedPublicKeyHex) {
try {
return await (0, orlp_ed25519_wasm_1.validateKeyPair)(privateKeyHex, expectedPublicKeyHex);
}
catch (error) {
return false;
}
}
}
exports.Ed25519SignatureVerifier = Ed25519SignatureVerifier;
//# sourceMappingURL=ed25519-verifier.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
import { CryptoKeyStore } from '../types/crypto';
export declare class MeshCoreKeyStore implements CryptoKeyStore {
nodeKeys: Map<string, string>;
private channelHashToKeys;
constructor(initialKeys?: {
channelSecrets?: string[];
nodeKeys?: Record<string, string>;
});
addNodeKey(publicKey: string, privateKey: string): void;
hasChannelKey(channelHash: string): boolean;
hasNodeKey(publicKey: string): boolean;
/**
* Get all channel keys that match the given channel hash (handles collisions)
*/
getChannelKeys(channelHash: string): string[];
getNodeKey(publicKey: string): string | undefined;
/**
* Add channel keys by secret keys (new simplified API)
* Automatically calculates channel hashes
*/
addChannelSecrets(secretKeys: string[]): void;
}
//# sourceMappingURL=key-manager.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"key-manager.d.ts","sourceRoot":"","sources":["../../src/crypto/key-manager.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAGjD,qBAAa,gBAAiB,YAAW,cAAc;IAC9C,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAa;IAGjD,OAAO,CAAC,iBAAiB,CAA+B;gBAE5C,WAAW,CAAC,EAAE;QACxB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACnC;IAYD,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI;IAKvD,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAK3C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAKtC;;OAEG;IACH,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE;IAK7C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAKjD;;;OAGG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI;CAW9C"}
@@ -0,0 +1,60 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.MeshCoreKeyStore = void 0;
const channel_crypto_1 = require("./channel-crypto");
class MeshCoreKeyStore {
constructor(initialKeys) {
this.nodeKeys = new Map();
// internal map for hash -> multiple keys (collision handling)
this.channelHashToKeys = new Map();
if (initialKeys?.channelSecrets) {
this.addChannelSecrets(initialKeys.channelSecrets);
}
if (initialKeys?.nodeKeys) {
Object.entries(initialKeys.nodeKeys).forEach(([pubKey, privKey]) => {
this.addNodeKey(pubKey, privKey);
});
}
}
addNodeKey(publicKey, privateKey) {
const normalizedPubKey = publicKey.toUpperCase();
this.nodeKeys.set(normalizedPubKey, privateKey);
}
hasChannelKey(channelHash) {
const normalizedHash = channelHash.toLowerCase();
return this.channelHashToKeys.has(normalizedHash);
}
hasNodeKey(publicKey) {
const normalizedPubKey = publicKey.toUpperCase();
return this.nodeKeys.has(normalizedPubKey);
}
/**
* Get all channel keys that match the given channel hash (handles collisions)
*/
getChannelKeys(channelHash) {
const normalizedHash = channelHash.toLowerCase();
return this.channelHashToKeys.get(normalizedHash) || [];
}
getNodeKey(publicKey) {
const normalizedPubKey = publicKey.toUpperCase();
return this.nodeKeys.get(normalizedPubKey);
}
/**
* Add channel keys by secret keys (new simplified API)
* Automatically calculates channel hashes
*/
addChannelSecrets(secretKeys) {
for (const secretKey of secretKeys) {
const channelHash = channel_crypto_1.ChannelCrypto.calculateChannelHash(secretKey).toLowerCase();
// Handle potential hash collisions
if (!this.channelHashToKeys.has(channelHash)) {
this.channelHashToKeys.set(channelHash, []);
}
this.channelHashToKeys.get(channelHash).push(secretKey);
}
}
}
exports.MeshCoreKeyStore = MeshCoreKeyStore;
//# sourceMappingURL=key-manager.js.map
@@ -0,0 +1 @@
{"version":3,"file":"key-manager.js","sourceRoot":"","sources":["../../src/crypto/key-manager.ts"],"names":[],"mappings":";AAAA,mFAAmF;AACnF,cAAc;;;AAGd,qDAAiD;AAEjD,MAAa,gBAAgB;IAM3B,YAAY,WAGX;QARM,aAAQ,GAAwB,IAAI,GAAG,EAAE,CAAC;QAEjD,8DAA8D;QACtD,sBAAiB,GAAG,IAAI,GAAG,EAAoB,CAAC;QAMtD,IAAI,WAAW,EAAE,cAAc,EAAE,CAAC;YAChC,IAAI,CAAC,iBAAiB,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;QACrD,CAAC;QAED,IAAI,WAAW,EAAE,QAAQ,EAAE,CAAC;YAC1B,MAAM,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE;gBACjE,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,UAAU,CAAC,SAAiB,EAAE,UAAkB;QAC9C,MAAM,gBAAgB,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QACjD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC;IAClD,CAAC;IAED,aAAa,CAAC,WAAmB;QAC/B,MAAM,cAAc,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACpD,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,MAAM,gBAAgB,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC;IAED;;OAEG;IACH,cAAc,CAAC,WAAmB;QAChC,MAAM,cAAc,GAAG,WAAW,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;IAC1D,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,MAAM,gBAAgB,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;QACjD,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC7C,CAAC;IAED;;;OAGG;IACH,iBAAiB,CAAC,UAAoB;QACpC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,WAAW,GAAG,8BAAa,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,CAAC;YAEhF,mCAAmC;YACnC,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC7C,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;YAC9C,CAAC;YACD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;CACF;AAhED,4CAgEC"}
@@ -0,0 +1,34 @@
/**
* Derive Ed25519 public key from private key using the exact orlp/ed25519 algorithm
*
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @returns 32-byte public key in hex format
*/
export declare function derivePublicKey(privateKeyHex: string): Promise<string>;
/**
* Validate that a private key and public key pair match using orlp/ed25519
*
* @param privateKeyHex - 64-byte private key in hex format
* @param expectedPublicKeyHex - 32-byte public key in hex format
* @returns true if the keys match, false otherwise
*/
export declare function validateKeyPair(privateKeyHex: string, expectedPublicKeyHex: string): Promise<boolean>;
/**
* Sign a message using Ed25519 with orlp/ed25519 implementation
*
* @param messageHex - Message to sign in hex format
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @param publicKeyHex - 32-byte public key in hex format
* @returns 64-byte signature in hex format
*/
export declare function sign(messageHex: string, privateKeyHex: string, publicKeyHex: string): Promise<string>;
/**
* Verify an Ed25519 signature using orlp/ed25519 implementation
*
* @param signatureHex - 64-byte signature in hex format
* @param messageHex - Message that was signed in hex format
* @param publicKeyHex - 32-byte public key in hex format
* @returns true if signature is valid, false otherwise
*/
export declare function verify(signatureHex: string, messageHex: string, publicKeyHex: string): Promise<boolean>;
//# sourceMappingURL=orlp-ed25519-wasm.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"orlp-ed25519-wasm.d.ts","sourceRoot":"","sources":["../../src/crypto/orlp-ed25519-wasm.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAiC5E;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,EAAE,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAoC3G;AAED;;;;;;;GAOG;AACH,wBAAsB,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAuC3G;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAsC7G"}
@@ -0,0 +1,150 @@
"use strict";
// WebAssembly wrapper for orlp/ed25519 key derivation
// This provides the exact orlp algorithm for JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.derivePublicKey = derivePublicKey;
exports.validateKeyPair = validateKeyPair;
exports.sign = sign;
exports.verify = verify;
const hex_1 = require("../utils/hex");
// Import the generated WASM module
const OrlpEd25519 = require('../../lib/orlp-ed25519.js');
/**
* Get a fresh WASM instance
* Loads a fresh instance each time because the WASM module could behave unpredictably otherwise
*/
async function getWasmInstance() {
return await OrlpEd25519();
}
/**
* Derive Ed25519 public key from private key using the exact orlp/ed25519 algorithm
*
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @returns 32-byte public key in hex format
*/
async function derivePublicKey(privateKeyHex) {
const wasmModule = await getWasmInstance();
const privateKeyBytes = (0, hex_1.hexToBytes)(privateKeyHex);
if (privateKeyBytes.length !== 64) {
throw new Error(`Invalid private key length: expected 64 bytes, got ${privateKeyBytes.length}`);
}
// Allocate memory buffers directly in WASM heap
const privateKeyPtr = 1024; // Use fixed memory locations
const publicKeyPtr = 1024 + 64;
// Copy private key to WASM memory
wasmModule.HEAPU8.set(privateKeyBytes, privateKeyPtr);
// Call the orlp key derivation function
const result = wasmModule.ccall('orlp_derive_public_key', 'number', ['number', 'number'], [publicKeyPtr, privateKeyPtr]);
if (result !== 0) {
throw new Error('orlp key derivation failed: invalid private key');
}
// Read the public key from WASM memory
const publicKeyBytes = new Uint8Array(32);
publicKeyBytes.set(wasmModule.HEAPU8.subarray(publicKeyPtr, publicKeyPtr + 32));
return (0, hex_1.bytesToHex)(publicKeyBytes);
}
/**
* Validate that a private key and public key pair match using orlp/ed25519
*
* @param privateKeyHex - 64-byte private key in hex format
* @param expectedPublicKeyHex - 32-byte public key in hex format
* @returns true if the keys match, false otherwise
*/
async function validateKeyPair(privateKeyHex, expectedPublicKeyHex) {
try {
const wasmModule = await getWasmInstance();
const privateKeyBytes = (0, hex_1.hexToBytes)(privateKeyHex);
const expectedPublicKeyBytes = (0, hex_1.hexToBytes)(expectedPublicKeyHex);
if (privateKeyBytes.length !== 64) {
return false;
}
if (expectedPublicKeyBytes.length !== 32) {
return false;
}
// Allocate memory buffers directly in WASM heap
const privateKeyPtr = 2048; // Use different fixed memory locations
const publicKeyPtr = 2048 + 64;
// Copy keys to WASM memory
wasmModule.HEAPU8.set(privateKeyBytes, privateKeyPtr);
wasmModule.HEAPU8.set(expectedPublicKeyBytes, publicKeyPtr);
// Call the validation function (note: C function expects public_key first, then private_key)
const result = wasmModule.ccall('orlp_validate_keypair', 'number', ['number', 'number'], [publicKeyPtr, privateKeyPtr]);
return result === 1;
}
catch (error) {
// Invalid hex strings or other errors should return false
return false;
}
}
/**
* Sign a message using Ed25519 with orlp/ed25519 implementation
*
* @param messageHex - Message to sign in hex format
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @param publicKeyHex - 32-byte public key in hex format
* @returns 64-byte signature in hex format
*/
async function sign(messageHex, privateKeyHex, publicKeyHex) {
const wasmModule = await getWasmInstance();
const messageBytes = (0, hex_1.hexToBytes)(messageHex);
const privateKeyBytes = (0, hex_1.hexToBytes)(privateKeyHex);
const publicKeyBytes = (0, hex_1.hexToBytes)(publicKeyHex);
if (privateKeyBytes.length !== 64) {
throw new Error(`Invalid private key length: expected 64 bytes, got ${privateKeyBytes.length}`);
}
if (publicKeyBytes.length !== 32) {
throw new Error(`Invalid public key length: expected 32 bytes, got ${publicKeyBytes.length}`);
}
// Allocate memory buffers with large gaps to avoid conflicts with scratch space
const messagePtr = 100000;
const privateKeyPtr = 200000;
const publicKeyPtr = 300000;
const signaturePtr = 400000;
// Copy data to WASM memory
wasmModule.HEAPU8.set(messageBytes, messagePtr);
wasmModule.HEAPU8.set(privateKeyBytes, privateKeyPtr);
wasmModule.HEAPU8.set(publicKeyBytes, publicKeyPtr);
// Call orlp_sign
wasmModule.ccall('orlp_sign', 'void', ['number', 'number', 'number', 'number', 'number'], [signaturePtr, messagePtr, messageBytes.length, publicKeyPtr, privateKeyPtr]);
// Read signature
const signatureBytes = new Uint8Array(64);
signatureBytes.set(wasmModule.HEAPU8.subarray(signaturePtr, signaturePtr + 64));
return (0, hex_1.bytesToHex)(signatureBytes);
}
/**
* Verify an Ed25519 signature using orlp/ed25519 implementation
*
* @param signatureHex - 64-byte signature in hex format
* @param messageHex - Message that was signed in hex format
* @param publicKeyHex - 32-byte public key in hex format
* @returns true if signature is valid, false otherwise
*/
async function verify(signatureHex, messageHex, publicKeyHex) {
try {
const wasmModule = await getWasmInstance();
const signatureBytes = (0, hex_1.hexToBytes)(signatureHex);
const messageBytes = (0, hex_1.hexToBytes)(messageHex);
const publicKeyBytes = (0, hex_1.hexToBytes)(publicKeyHex);
if (signatureBytes.length !== 64) {
return false;
}
if (publicKeyBytes.length !== 32) {
return false;
}
// Allocate memory buffers with large gaps to avoid conflicts with scratch space
const messagePtr = 500000;
const signaturePtr = 600000;
const publicKeyPtr = 700000;
// Copy data to WASM memory
wasmModule.HEAPU8.set(signatureBytes, signaturePtr);
wasmModule.HEAPU8.set(messageBytes, messagePtr);
wasmModule.HEAPU8.set(publicKeyBytes, publicKeyPtr);
// Call the orlp verify function
const result = wasmModule.ccall('orlp_verify', 'number', ['number', 'number', 'number', 'number'], [signaturePtr, messagePtr, messageBytes.length, publicKeyPtr]);
return result === 1;
}
catch (error) {
return false;
}
}
//# sourceMappingURL=orlp-ed25519-wasm.js.map
@@ -0,0 +1 @@
{"version":3,"file":"orlp-ed25519-wasm.js","sourceRoot":"","sources":["../../src/crypto/orlp-ed25519-wasm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,51 @@
import { DecodedPacket, PacketStructure } from '../types/packet';
import { DecryptionOptions, ValidationResult, CryptoKeyStore } from '../types/crypto';
export declare class MeshCorePacketDecoder {
/**
* Decode a raw packet from hex string
*/
static decode(hexData: string, options?: DecryptionOptions): DecodedPacket;
/**
* Decode a raw packet from hex string with signature verification for advertisements
*/
static decodeWithVerification(hexData: string, options?: DecryptionOptions): Promise<DecodedPacket>;
/**
* Analyze packet structure for detailed breakdown
*/
static analyzeStructure(hexData: string, options?: DecryptionOptions): PacketStructure;
/**
* Analyze packet structure for detailed breakdown with signature verification for advertisements
*/
static analyzeStructureWithVerification(hexData: string, options?: DecryptionOptions): Promise<PacketStructure>;
/**
* Internal unified parsing method
*/
private static parseInternal;
/**
* Internal unified parsing method with signature verification for advertisements
*/
private static parseInternalAsync;
/**
* Validate packet format without full decoding
*/
static validate(hexData: string): ValidationResult;
/**
* Calculate message hash for a packet
*/
static calculateMessageHash(bytes: Uint8Array, routeType: number, payloadType: number, payloadVersion: number): string;
/**
* Create a key store for decryption
*/
static createKeyStore(initialKeys?: {
channelSecrets?: string[];
nodeKeys?: Record<string, string>;
}): CryptoKeyStore;
/**
* Decode a path_len byte into hash size, hop count, and total byte length.
* Firmware reference: Packet.h lines 79-83
* Bits 7:6 = hash size selector: (path_len >> 6) + 1 = 1, 2, or 3 bytes per hop
* Bits 5:0 = hop count (0-63)
*/
private static decodePathLenByte;
}
//# sourceMappingURL=packet-decoder.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"packet-decoder.d.ts","sourceRoot":"","sources":["../../src/decoder/packet-decoder.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,eAAe,EAAkD,MAAM,iBAAiB,CAAC;AAIjH,OAAO,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAatF,qBAAa,qBAAqB;IAChC;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,aAAa;IAK1E;;OAEG;WACU,sBAAsB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,aAAa,CAAC;IAKzG;;OAEG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,eAAe;IAKtF;;OAEG;WACU,gCAAgC,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,eAAe,CAAC;IAKrH;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IA6Y5B;;OAEG;mBACkB,kBAAkB;IA2CvC;;OAEG;IACH,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB;IAmDlD;;OAEG;IACH,MAAM,CAAC,oBAAoB,CAAC,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM;IAkDtH;;OAEG;IACH,MAAM,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE;QAClC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACnC,GAAG,cAAc;IAIlB;;;;;OAKG;IACH,OAAO,CAAC,MAAM,CAAC,iBAAiB;CAMjC"}
@@ -0,0 +1,576 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.MeshCorePacketDecoder = void 0;
const enums_1 = require("../types/enums");
const hex_1 = require("../utils/hex");
const enum_names_1 = require("../utils/enum-names");
const key_manager_1 = require("../crypto/key-manager");
const advert_1 = require("./payload-decoders/advert");
const trace_1 = require("./payload-decoders/trace");
const group_text_1 = require("./payload-decoders/group-text");
const request_1 = require("./payload-decoders/request");
const response_1 = require("./payload-decoders/response");
const anon_request_1 = require("./payload-decoders/anon-request");
const ack_1 = require("./payload-decoders/ack");
const path_1 = require("./payload-decoders/path");
const text_message_1 = require("./payload-decoders/text-message");
const control_1 = require("./payload-decoders/control");
class MeshCorePacketDecoder {
/**
* Decode a raw packet from hex string
*/
static decode(hexData, options) {
const result = this.parseInternal(hexData, false, options);
return result.packet;
}
/**
* Decode a raw packet from hex string with signature verification for advertisements
*/
static async decodeWithVerification(hexData, options) {
const result = await this.parseInternalAsync(hexData, false, options);
return result.packet;
}
/**
* Analyze packet structure for detailed breakdown
*/
static analyzeStructure(hexData, options) {
const result = this.parseInternal(hexData, true, options);
return result.structure;
}
/**
* Analyze packet structure for detailed breakdown with signature verification for advertisements
*/
static async analyzeStructureWithVerification(hexData, options) {
const result = await this.parseInternalAsync(hexData, true, options);
return result.structure;
}
/**
* Internal unified parsing method
*/
static parseInternal(hexData, includeStructure, options) {
const bytes = (0, hex_1.hexToBytes)(hexData);
const segments = [];
if (bytes.length < 2) {
const errorPacket = {
messageHash: '',
routeType: enums_1.RouteType.Flood,
payloadType: enums_1.PayloadType.RawCustom,
payloadVersion: enums_1.PayloadVersion.Version1,
pathLength: 0,
path: null,
payload: { raw: '', decoded: null },
totalBytes: bytes.length,
isValid: false,
errors: ['Packet too short (minimum 2 bytes required)']
};
const errorStructure = {
segments: [],
totalBytes: bytes.length,
rawHex: hexData.toUpperCase(),
messageHash: '',
payload: {
segments: [],
hex: '',
startByte: 0,
type: 'Unknown'
}
};
return { packet: errorPacket, structure: errorStructure };
}
try {
let offset = 0;
// parse header
const header = bytes[0];
const routeType = header & 0x03;
const payloadType = (header >> 2) & 0x0F;
const payloadVersion = (header >> 6) & 0x03;
if (includeStructure) {
segments.push({
name: 'Header',
description: 'Header byte breakdown',
startByte: 0,
endByte: 0,
value: `0x${header.toString(16).padStart(2, '0')}`,
headerBreakdown: {
fullBinary: header.toString(2).padStart(8, '0'),
fields: [
{
bits: '0-1',
field: 'Route Type',
value: (0, enum_names_1.getRouteTypeName)(routeType),
binary: (header & 0x03).toString(2).padStart(2, '0')
},
{
bits: '2-5',
field: 'Payload Type',
value: (0, enum_names_1.getPayloadTypeName)(payloadType),
binary: ((header >> 2) & 0x0F).toString(2).padStart(4, '0')
},
{
bits: '6-7',
field: 'Version',
value: payloadVersion.toString(),
binary: ((header >> 6) & 0x03).toString(2).padStart(2, '0')
}
]
}
});
}
offset = 1;
// handle transport codes
let transportCodes;
if (routeType === enums_1.RouteType.TransportFlood || routeType === enums_1.RouteType.TransportDirect) {
if (bytes.length < offset + 4) {
throw new Error('Packet too short for transport codes');
}
const code1 = bytes[offset] | (bytes[offset + 1] << 8);
const code2 = bytes[offset + 2] | (bytes[offset + 3] << 8);
transportCodes = [code1, code2];
if (includeStructure) {
const transportCode = (bytes[offset]) | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24);
segments.push({
name: 'Transport Code',
description: 'Used for Direct/Response routing',
startByte: offset,
endByte: offset + 3,
value: `0x${transportCode.toString(16).padStart(8, '0')}`
});
}
offset += 4;
}
// parse path length byte (encodes hash size and hop count)
// Bits 7:6 = hash size selector: (path_len >> 6) + 1 = 1, 2, or 3 bytes per hop
// Bits 5:0 = hop count (0-63)
if (bytes.length < offset + 1) {
throw new Error('Packet too short for path length');
}
const pathLenByte = bytes[offset];
const { hashSize: pathHashSize, hopCount: pathHopCount, byteLength: pathByteLength } = this.decodePathLenByte(pathLenByte);
if (pathHashSize === 4) {
throw new Error('Invalid path length byte: reserved hash size (bits 7:6 = 11)');
}
if (includeStructure) {
const hashDesc = pathHashSize > 1 ? ` × ${pathHashSize}-byte hashes (${pathByteLength} bytes)` : '';
let pathLengthDescription;
if (pathHopCount === 0) {
pathLengthDescription = pathHashSize > 1 ? `No path data (${pathHashSize}-byte hash mode)` : 'No path data';
}
else if (routeType === enums_1.RouteType.Direct || routeType === enums_1.RouteType.TransportDirect) {
pathLengthDescription = `${pathHopCount} hops${hashDesc} of routing instructions (decreases as packet travels)`;
}
else if (routeType === enums_1.RouteType.Flood || routeType === enums_1.RouteType.TransportFlood) {
pathLengthDescription = `${pathHopCount} hops${hashDesc} showing route taken (increases as packet floods)`;
}
else {
pathLengthDescription = `Path contains ${pathHopCount} hops${hashDesc}`;
}
segments.push({
name: 'Path Length',
description: pathLengthDescription,
startByte: offset,
endByte: offset,
value: `0x${pathLenByte.toString(16).padStart(2, '0')}`,
headerBreakdown: {
fullBinary: pathLenByte.toString(2).padStart(8, '0'),
fields: [
{
bits: '6-7',
field: 'Hash Size',
value: `${pathHashSize} byte${pathHashSize > 1 ? 's' : ''} per hop`,
binary: ((pathLenByte >> 6) & 0x03).toString(2).padStart(2, '0')
},
{
bits: '0-5',
field: 'Hop Count',
value: `${pathHopCount} hop${pathHopCount !== 1 ? 's' : ''}`,
binary: (pathLenByte & 63).toString(2).padStart(6, '0')
}
]
}
});
}
offset += 1;
if (bytes.length < offset + pathByteLength) {
throw new Error('Packet too short for path data');
}
// convert path data to grouped hex strings (one entry per hop)
const pathBytes = bytes.subarray(offset, offset + pathByteLength);
let path = null;
if (pathHopCount > 0) {
path = [];
for (let i = 0; i < pathHopCount; i++) {
const hopBytes = pathBytes.subarray(i * pathHashSize, (i + 1) * pathHashSize);
path.push((0, hex_1.bytesToHex)(hopBytes));
}
}
if (includeStructure && pathHopCount > 0) {
if (payloadType === enums_1.PayloadType.Trace) {
// TRACE packets have SNR values in path (always single-byte entries)
const snrValues = [];
for (let i = 0; i < pathByteLength; i++) {
const snrRaw = bytes[offset + i];
const snrSigned = snrRaw > 127 ? snrRaw - 256 : snrRaw;
const snrDb = snrSigned / 4.0;
snrValues.push(`${snrDb.toFixed(2)}dB (0x${snrRaw.toString(16).padStart(2, '0')})`);
}
segments.push({
name: 'Path SNR Data',
description: `SNR values collected during trace: ${snrValues.join(', ')}`,
startByte: offset,
endByte: offset + pathByteLength - 1,
value: (0, hex_1.bytesToHex)(bytes.slice(offset, offset + pathByteLength))
});
}
else {
let pathDescription = 'Routing path information';
if (routeType === enums_1.RouteType.Direct || routeType === enums_1.RouteType.TransportDirect) {
pathDescription = `Routing instructions (${pathHashSize}-byte hashes stripped at each hop as packet travels to destination)`;
}
else if (routeType === enums_1.RouteType.Flood || routeType === enums_1.RouteType.TransportFlood) {
pathDescription = `Historical route taken (${pathHashSize}-byte hashes added as packet floods through network)`;
}
segments.push({
name: 'Path Data',
description: pathDescription,
startByte: offset,
endByte: offset + pathByteLength - 1,
value: (0, hex_1.bytesToHex)(bytes.slice(offset, offset + pathByteLength))
});
}
}
offset += pathByteLength;
// extract payload
const payloadBytes = bytes.subarray(offset);
const payloadHex = (0, hex_1.bytesToHex)(payloadBytes);
if (includeStructure && bytes.length > offset) {
segments.push({
name: 'Payload',
description: `${(0, enum_names_1.getPayloadTypeName)(payloadType)} payload data`,
startByte: offset,
endByte: bytes.length - 1,
value: (0, hex_1.bytesToHex)(bytes.slice(offset))
});
}
// decode payload based on type and optionally get segments in one pass
let decodedPayload = null;
const payloadSegments = [];
if (payloadType === enums_1.PayloadType.Advert) {
const result = advert_1.AdvertPayloadDecoder.decode(payloadBytes, {
includeSegments: includeStructure,
segmentOffset: 0
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
else if (payloadType === enums_1.PayloadType.Trace) {
const result = trace_1.TracePayloadDecoder.decode(payloadBytes, path, {
includeSegments: includeStructure,
segmentOffset: 0 // Payload segments are relative to payload start
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments; // Remove from decoded payload to keep it clean
}
}
else if (payloadType === enums_1.PayloadType.GroupText) {
const result = group_text_1.GroupTextPayloadDecoder.decode(payloadBytes, {
...options,
includeSegments: includeStructure,
segmentOffset: 0
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
else if (payloadType === enums_1.PayloadType.Request) {
const result = request_1.RequestPayloadDecoder.decode(payloadBytes, {
includeSegments: includeStructure,
segmentOffset: 0 // Payload segments are relative to payload start
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
else if (payloadType === enums_1.PayloadType.Response) {
const result = response_1.ResponsePayloadDecoder.decode(payloadBytes, {
includeSegments: includeStructure,
segmentOffset: 0 // Payload segments are relative to payload start
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
else if (payloadType === enums_1.PayloadType.AnonRequest) {
const result = anon_request_1.AnonRequestPayloadDecoder.decode(payloadBytes, {
includeSegments: includeStructure,
segmentOffset: 0
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
else if (payloadType === enums_1.PayloadType.Ack) {
const result = ack_1.AckPayloadDecoder.decode(payloadBytes, {
includeSegments: includeStructure,
segmentOffset: 0
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
else if (payloadType === enums_1.PayloadType.Path) {
decodedPayload = path_1.PathPayloadDecoder.decode(payloadBytes);
}
else if (payloadType === enums_1.PayloadType.TextMessage) {
const result = text_message_1.TextMessagePayloadDecoder.decode(payloadBytes, {
includeSegments: includeStructure,
segmentOffset: 0
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
else if (payloadType === enums_1.PayloadType.Control) {
const result = control_1.ControlPayloadDecoder.decode(payloadBytes, {
includeSegments: includeStructure,
segmentOffset: 0
});
decodedPayload = result;
if (result?.segments) {
payloadSegments.push(...result.segments);
delete result.segments;
}
}
// if no segments were generated and we need structure, show basic payload info
if (includeStructure && payloadSegments.length === 0 && bytes.length > offset) {
payloadSegments.push({
name: `${(0, enum_names_1.getPayloadTypeName)(payloadType)} Payload`,
description: `Raw ${(0, enum_names_1.getPayloadTypeName)(payloadType)} payload data (${payloadBytes.length} bytes)`,
startByte: 0,
endByte: payloadBytes.length - 1,
value: (0, hex_1.bytesToHex)(payloadBytes)
});
}
// calculate message hash
const messageHash = this.calculateMessageHash(bytes, routeType, payloadType, payloadVersion);
const packet = {
messageHash,
routeType,
payloadType,
payloadVersion,
transportCodes,
pathLength: pathHopCount,
...(pathHashSize > 1 ? { pathHashSize } : {}),
path,
payload: {
raw: payloadHex,
decoded: decodedPayload
},
totalBytes: bytes.length,
isValid: true
};
const structure = {
segments,
totalBytes: bytes.length,
rawHex: hexData.toUpperCase(),
messageHash,
payload: {
segments: payloadSegments,
hex: payloadHex,
startByte: offset,
type: (0, enum_names_1.getPayloadTypeName)(payloadType)
}
};
return { packet, structure };
}
catch (error) {
const errorPacket = {
messageHash: '',
routeType: enums_1.RouteType.Flood,
payloadType: enums_1.PayloadType.RawCustom,
payloadVersion: enums_1.PayloadVersion.Version1,
pathLength: 0,
path: null,
payload: { raw: '', decoded: null },
totalBytes: bytes.length,
isValid: false,
errors: [error instanceof Error ? error.message : 'Unknown decoding error']
};
const errorStructure = {
segments: [],
totalBytes: bytes.length,
rawHex: hexData.toUpperCase(),
messageHash: '',
payload: {
segments: [],
hex: '',
startByte: 0,
type: 'Unknown'
}
};
return { packet: errorPacket, structure: errorStructure };
}
}
/**
* Internal unified parsing method with signature verification for advertisements
*/
static async parseInternalAsync(hexData, includeStructure, options) {
// First do the regular parsing
const result = this.parseInternal(hexData, includeStructure, options);
// If it's an advertisement, verify the signature
if (result.packet.payloadType === enums_1.PayloadType.Advert && result.packet.payload.decoded) {
try {
const advertPayload = result.packet.payload.decoded;
const verifiedAdvert = await advert_1.AdvertPayloadDecoder.decodeWithVerification((0, hex_1.hexToBytes)(result.packet.payload.raw), {
includeSegments: includeStructure,
segmentOffset: 0
});
if (verifiedAdvert) {
// Update the payload with signature verification results
result.packet.payload.decoded = verifiedAdvert;
// If the advertisement signature is invalid, mark the whole packet as invalid
if (!verifiedAdvert.isValid) {
result.packet.isValid = false;
result.packet.errors = verifiedAdvert.errors || ['Invalid advertisement signature'];
}
// Update structure segments if needed
if (includeStructure && verifiedAdvert.segments) {
result.structure.payload.segments = verifiedAdvert.segments;
delete verifiedAdvert.segments;
}
}
}
catch (error) {
console.error('Signature verification failed:', error);
}
}
return result;
}
/**
* Validate packet format without full decoding
*/
static validate(hexData) {
const bytes = (0, hex_1.hexToBytes)(hexData);
const errors = [];
if (bytes.length < 2) {
errors.push('Packet too short (minimum 2 bytes required)');
return { isValid: false, errors };
}
try {
let offset = 1; // Skip header
// check transport codes
const header = bytes[0];
const routeType = header & 0x03;
if (routeType === enums_1.RouteType.TransportFlood || routeType === enums_1.RouteType.TransportDirect) {
if (bytes.length < offset + 4) {
errors.push('Packet too short for transport codes');
}
offset += 4;
}
// check path length
if (bytes.length < offset + 1) {
errors.push('Packet too short for path length');
}
else {
const pathLenByte = bytes[offset];
const { hashSize, byteLength } = this.decodePathLenByte(pathLenByte);
offset += 1;
if (hashSize === 4) {
errors.push('Invalid path length byte: reserved hash size (bits 7:6 = 11)');
}
if (bytes.length < offset + byteLength) {
errors.push('Packet too short for path data');
}
offset += byteLength;
}
// check if we have payload data
if (offset >= bytes.length) {
errors.push('No payload data found');
}
}
catch (error) {
errors.push(error instanceof Error ? error.message : 'Validation error');
}
return { isValid: errors.length === 0, errors: errors.length > 0 ? errors : undefined };
}
/**
* Calculate message hash for a packet
*/
static calculateMessageHash(bytes, routeType, payloadType, payloadVersion) {
// for TRACE packets, use the trace tag as hash
if (payloadType === enums_1.PayloadType.Trace && bytes.length >= 13) {
let offset = 1;
// skip transport codes if present
if (routeType === enums_1.RouteType.TransportFlood || routeType === enums_1.RouteType.TransportDirect) {
offset += 4;
}
// skip path data (decode path_len byte for multi-byte hops)
if (bytes.length > offset) {
const { byteLength } = this.decodePathLenByte(bytes[offset]);
offset += 1 + byteLength;
}
// extract trace tag
if (bytes.length >= offset + 4) {
const traceTag = (bytes[offset]) | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24);
return (0, hex_1.numberToHex)(traceTag, 8);
}
}
// for other packets, create hash from constant parts
const constantHeader = (payloadType << 2) | (payloadVersion << 6);
let offset = 1;
// skip transport codes if present
if (routeType === enums_1.RouteType.TransportFlood || routeType === enums_1.RouteType.TransportDirect) {
offset += 4;
}
// skip path data (decode path_len byte for multi-byte hops)
if (bytes.length > offset) {
const { byteLength } = this.decodePathLenByte(bytes[offset]);
offset += 1 + byteLength;
}
const payloadData = bytes.slice(offset);
const hashInput = [constantHeader, ...Array.from(payloadData)];
// generate hash
let hash = 0;
for (let i = 0; i < hashInput.length; i++) {
hash = ((hash << 5) - hash + hashInput[i]) & 0xffffffff;
}
return (0, hex_1.numberToHex)(hash, 8);
}
/**
* Create a key store for decryption
*/
static createKeyStore(initialKeys) {
return new key_manager_1.MeshCoreKeyStore(initialKeys);
}
/**
* Decode a path_len byte into hash size, hop count, and total byte length.
* Firmware reference: Packet.h lines 79-83
* Bits 7:6 = hash size selector: (path_len >> 6) + 1 = 1, 2, or 3 bytes per hop
* Bits 5:0 = hop count (0-63)
*/
static decodePathLenByte(pathLenByte) {
const hashSize = (pathLenByte >> 6) + 1;
const hopCount = pathLenByte & 63;
return { hashSize, hopCount, byteLength: hopCount * hashSize };
}
}
exports.MeshCorePacketDecoder = MeshCorePacketDecoder;
//# sourceMappingURL=packet-decoder.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
import { AckPayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class AckPayloadDecoder {
static decode(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): AckPayload & {
segments?: PayloadSegment[];
} | null;
}
//# sourceMappingURL=ack.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"ack.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/ack.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD,qBAAa,iBAAiB;IAC5B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,UAAU,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;CA2EzJ"}
@@ -0,0 +1,78 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.AckPayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
class AckPayloadDecoder {
static decode(payload, options) {
try {
// Based on MeshCore payloads.md - Ack payload structure:
// - checksum (4 bytes) - CRC checksum of message timestamp, text, and sender pubkey
if (payload.length < 4) {
const result = {
type: enums_1.PayloadType.Ack,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['Ack payload too short (minimum 4 bytes for checksum)'],
checksum: ''
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid Ack Data',
description: 'Ack payload too short (minimum 4 bytes required for checksum)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
// parse checksum (4 bytes as hex)
const checksum = (0, hex_1.bytesToHex)(payload.subarray(0, 4));
if (options?.includeSegments) {
segments.push({
name: 'Checksum',
description: `CRC checksum of message timestamp, text, and sender pubkey: 0x${checksum}`,
startByte: segmentOffset,
endByte: segmentOffset + 3,
value: checksum
});
}
// any additional data (if present)
if (options?.includeSegments && payload.length > 4) {
segments.push({
name: 'Additional Data',
description: 'Extra data in Ack payload',
startByte: segmentOffset + 4,
endByte: segmentOffset + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload.subarray(4))
});
}
const result = {
type: enums_1.PayloadType.Ack,
version: enums_1.PayloadVersion.Version1,
isValid: true,
checksum
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
catch (error) {
return {
type: enums_1.PayloadType.Ack,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode Ack payload'],
checksum: ''
};
}
}
}
exports.AckPayloadDecoder = AckPayloadDecoder;
//# sourceMappingURL=ack.js.map
@@ -0,0 +1 @@
{"version":3,"file":"ack.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/ack.ts"],"names":[],"mappings":";AAAA,mFAAmF;AACnF,cAAc;;;AAId,6CAAgE;AAChE,yCAA6C;AAE7C,MAAa,iBAAiB;IAC5B,MAAM,CAAC,MAAM,CAAC,OAAmB,EAAE,OAA+D;QAChG,IAAI,CAAC;YACH,yDAAyD;YACzD,oFAAoF;YAEpF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAiD;oBAC3D,IAAI,EAAE,mBAAW,CAAC,GAAG;oBACrB,OAAO,EAAE,sBAAc,CAAC,QAAQ;oBAChC,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,CAAC,sDAAsD,CAAC;oBAChE,QAAQ,EAAE,EAAE;iBACb,CAAC;gBAEF,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;oBAC7B,MAAM,CAAC,QAAQ,GAAG,CAAC;4BACjB,IAAI,EAAE,kBAAkB;4BACxB,WAAW,EAAE,+DAA+D;4BAC5E,SAAS,EAAE,OAAO,CAAC,aAAa,IAAI,CAAC;4BACrC,OAAO,EAAE,CAAC,OAAO,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC;4BAC1D,KAAK,EAAE,IAAA,gBAAU,EAAC,OAAO,CAAC;yBAC3B,CAAC,CAAC;gBACL,CAAC;gBAED,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,MAAM,QAAQ,GAAqB,EAAE,CAAC;YACtC,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,CAAC,CAAC;YAElD,kCAAkC;YAClC,MAAM,QAAQ,GAAG,IAAA,gBAAU,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YACpD,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,UAAU;oBAChB,WAAW,EAAE,iEAAiE,QAAQ,EAAE;oBACxF,SAAS,EAAE,aAAa;oBACxB,OAAO,EAAE,aAAa,GAAG,CAAC;oBAC1B,KAAK,EAAE,QAAQ;iBAChB,CAAC,CAAC;YACL,CAAC;YAED,mCAAmC;YACnC,IAAI,OAAO,EAAE,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACnD,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,iBAAiB;oBACvB,WAAW,EAAE,2BAA2B;oBACxC,SAAS,EAAE,aAAa,GAAG,CAAC;oBAC5B,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC;oBAC3C,KAAK,EAAE,IAAA,gBAAU,EAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;iBACvC,CAAC,CAAC;YACL,CAAC;YAED,MAAM,MAAM,GAAiD;gBAC3D,IAAI,EAAE,mBAAW,CAAC,GAAG;gBACrB,OAAO,EAAE,sBAAc,CAAC,QAAQ;gBAChC,OAAO,EAAE,IAAI;gBACb,QAAQ;aACT,CAAC;YAEF,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,IAAI,EAAE,mBAAW,CAAC,GAAG;gBACrB,OAAO,EAAE,sBAAc,CAAC,QAAQ;gBAChC,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,8BAA8B,CAAC;gBACjF,QAAQ,EAAE,EAAE;aACb,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA5ED,8CA4EC"}
@@ -0,0 +1,24 @@
import { AdvertPayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class AdvertPayloadDecoder {
static decode(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): AdvertPayload & {
segments?: PayloadSegment[];
} | null;
/**
* Decode advertisement payload with signature verification
*/
static decodeWithVerification(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): Promise<AdvertPayload & {
segments?: PayloadSegment[];
} | null>;
private static parseDeviceRole;
private static readUint32LE;
private static readInt32LE;
private static sanitizeControlCharacters;
}
//# sourceMappingURL=advert.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"advert.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/advert.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAMpD,qBAAa,oBAAoB;IAC/B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;IAuL3J;;OAEG;WACU,sBAAsB,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI,CAAC;IA4C1L,OAAO,CAAC,MAAM,CAAC,eAAe;IAW9B,OAAO,CAAC,MAAM,CAAC,YAAY;IAO3B,OAAO,CAAC,MAAM,CAAC,WAAW;IAM1B,OAAO,CAAC,MAAM,CAAC,yBAAyB;CAKzC"}
@@ -0,0 +1,244 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.AdvertPayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
const enum_names_1 = require("../../utils/enum-names");
const ed25519_verifier_1 = require("../../crypto/ed25519-verifier");
class AdvertPayloadDecoder {
static decode(payload, options) {
try {
// start of appdata section: public_key(32) + timestamp(4) + signature(64) + flags(1) = 101 bytes
if (payload.length < 101) {
const result = {
type: enums_1.PayloadType.Advert,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['Advertisement payload too short'],
publicKey: '',
timestamp: 0,
signature: '',
appData: {
flags: 0,
deviceRole: enums_1.DeviceRole.ChatNode,
hasLocation: false,
hasName: false
}
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid Advert Data',
description: 'Advert payload too short (minimum 101 bytes required)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
let currentOffset = 0;
// parse advertisement structure from payloads.md
const publicKey = (0, hex_1.bytesToHex)(payload.subarray(currentOffset, currentOffset + 32));
if (options?.includeSegments) {
segments.push({
name: 'Public Key',
description: 'Ed25519 public key',
startByte: segmentOffset + currentOffset,
endByte: segmentOffset + currentOffset + 31,
value: publicKey
});
}
currentOffset += 32;
const timestamp = this.readUint32LE(payload, currentOffset);
if (options?.includeSegments) {
const timestampDate = new Date(timestamp * 1000);
segments.push({
name: 'Timestamp',
description: `${timestamp} (${timestampDate.toISOString().slice(0, 19)}Z)`,
startByte: segmentOffset + currentOffset,
endByte: segmentOffset + currentOffset + 3,
value: (0, hex_1.bytesToHex)(payload.subarray(currentOffset, currentOffset + 4))
});
}
currentOffset += 4;
const signature = (0, hex_1.bytesToHex)(payload.subarray(currentOffset, currentOffset + 64));
if (options?.includeSegments) {
segments.push({
name: 'Signature',
description: 'Ed25519 signature',
startByte: segmentOffset + currentOffset,
endByte: segmentOffset + currentOffset + 63,
value: signature
});
}
currentOffset += 64;
const flags = payload[currentOffset];
if (options?.includeSegments) {
const binaryStr = flags.toString(2).padStart(8, '0');
const deviceRole = this.parseDeviceRole(flags);
const roleName = (0, enum_names_1.getDeviceRoleName)(deviceRole);
const flagDesc = ` | Bits 0-3 (Role): ${roleName} | Bit 4 (Location): ${!!(flags & enums_1.AdvertFlags.HasLocation) ? 'Yes' : 'No'} | Bit 7 (Name): ${!!(flags & enums_1.AdvertFlags.HasName) ? 'Yes' : 'No'}`;
segments.push({
name: 'App Flags',
description: `Binary: ${binaryStr}${flagDesc}`,
startByte: segmentOffset + currentOffset,
endByte: segmentOffset + currentOffset,
value: flags.toString(16).padStart(2, '0').toUpperCase()
});
}
currentOffset += 1;
const advert = {
type: enums_1.PayloadType.Advert,
version: enums_1.PayloadVersion.Version1,
isValid: true,
publicKey,
timestamp,
signature,
appData: {
flags,
deviceRole: this.parseDeviceRole(flags),
hasLocation: !!(flags & enums_1.AdvertFlags.HasLocation),
hasName: !!(flags & enums_1.AdvertFlags.HasName)
}
};
let offset = currentOffset;
// location data (if HasLocation flag is set)
if (flags & enums_1.AdvertFlags.HasLocation && payload.length >= offset + 8) {
const lat = this.readInt32LE(payload, offset) / 1000000;
const lon = this.readInt32LE(payload, offset + 4) / 1000000;
advert.appData.location = {
latitude: Math.round(lat * 1000000) / 1000000, // Keep precision
longitude: Math.round(lon * 1000000) / 1000000
};
if (options?.includeSegments) {
segments.push({
name: 'Latitude',
description: `${lat}° (${lat})`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 3,
value: (0, hex_1.bytesToHex)(payload.subarray(offset, offset + 4))
});
segments.push({
name: 'Longitude',
description: `${lon}° (${lon})`,
startByte: segmentOffset + offset + 4,
endByte: segmentOffset + offset + 7,
value: (0, hex_1.bytesToHex)(payload.subarray(offset + 4, offset + 8))
});
}
offset += 8;
}
// skip feature fields for now (HasFeature1, HasFeature2)
if (flags & enums_1.AdvertFlags.HasFeature1)
offset += 2;
if (flags & enums_1.AdvertFlags.HasFeature2)
offset += 2;
// name data (if HasName flag is set)
if (flags & enums_1.AdvertFlags.HasName && payload.length > offset) {
const nameBytes = payload.subarray(offset);
const rawName = new TextDecoder('utf-8').decode(nameBytes).replace(/\0.*$/, '');
advert.appData.name = this.sanitizeControlCharacters(rawName) || rawName;
if (options?.includeSegments) {
segments.push({
name: 'Node Name',
description: `Node name: "${advert.appData.name}"`,
startByte: segmentOffset + offset,
endByte: segmentOffset + payload.length - 1,
value: (0, hex_1.bytesToHex)(nameBytes)
});
}
}
if (options?.includeSegments) {
advert.segments = segments;
}
return advert;
}
catch (error) {
return {
type: enums_1.PayloadType.Advert,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode advertisement payload'],
publicKey: '',
timestamp: 0,
signature: '',
appData: {
flags: 0,
deviceRole: enums_1.DeviceRole.ChatNode,
hasLocation: false,
hasName: false
}
};
}
}
/**
* Decode advertisement payload with signature verification
*/
static async decodeWithVerification(payload, options) {
// First decode normally
const advert = this.decode(payload, options);
if (!advert || !advert.isValid) {
return advert;
}
// Perform signature verification
try {
// Extract app_data from the payload (everything after public_key + timestamp + signature)
const appDataStart = 32 + 4 + 64; // public_key + timestamp + signature
const appDataBytes = payload.subarray(appDataStart);
const appDataHex = (0, hex_1.bytesToHex)(appDataBytes);
const signatureValid = await ed25519_verifier_1.Ed25519SignatureVerifier.verifyAdvertisementSignature(advert.publicKey, advert.signature, advert.timestamp, appDataHex);
advert.signatureValid = signatureValid;
if (!signatureValid) {
advert.signatureError = 'Ed25519 signature verification failed';
advert.isValid = false;
if (!advert.errors) {
advert.errors = [];
}
advert.errors.push('Invalid Ed25519 signature');
}
}
catch (error) {
advert.signatureValid = false;
advert.signatureError = error instanceof Error ? error.message : 'Signature verification error';
advert.isValid = false;
if (!advert.errors) {
advert.errors = [];
}
advert.errors.push('Signature verification failed: ' + (error instanceof Error ? error.message : 'Unknown error'));
}
return advert;
}
static parseDeviceRole(flags) {
const roleValue = flags & 0x0F;
switch (roleValue) {
case 0x01: return enums_1.DeviceRole.ChatNode;
case 0x02: return enums_1.DeviceRole.Repeater;
case 0x03: return enums_1.DeviceRole.RoomServer;
case 0x04: return enums_1.DeviceRole.Sensor;
default: return enums_1.DeviceRole.ChatNode;
}
}
static readUint32LE(buffer, offset) {
return buffer[offset] |
(buffer[offset + 1] << 8) |
(buffer[offset + 2] << 16) |
(buffer[offset + 3] << 24);
}
static readInt32LE(buffer, offset) {
const value = this.readUint32LE(buffer, offset);
// convert unsigned to signed
return value > 0x7FFFFFFF ? value - 0x100000000 : value;
}
static sanitizeControlCharacters(value) {
if (!value)
return null;
const sanitized = value.trim().replace(/[\x00-\x1F\x7F]/g, '');
return sanitized || null;
}
}
exports.AdvertPayloadDecoder = AdvertPayloadDecoder;
//# sourceMappingURL=advert.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,11 @@
import { AnonRequestPayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class AnonRequestPayloadDecoder {
static decode(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): AnonRequestPayload & {
segments?: PayloadSegment[];
} | null;
}
//# sourceMappingURL=anon-request.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"anon-request.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/anon-request.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD,qBAAa,yBAAyB;IACpC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,kBAAkB,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;CA8HjK"}
@@ -0,0 +1,123 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnonRequestPayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
class AnonRequestPayloadDecoder {
static decode(payload, options) {
try {
// Based on MeshCore payloads.md - AnonRequest payload structure:
// - destination_hash (1 byte)
// - sender_public_key (32 bytes)
// - cipher_mac (2 bytes)
// - ciphertext (rest of payload)
if (payload.length < 35) {
const result = {
type: enums_1.PayloadType.AnonRequest,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['AnonRequest payload too short (minimum 35 bytes: dest + public key + MAC)'],
destinationHash: '',
senderPublicKey: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid AnonRequest Data',
description: 'AnonRequest payload too short (minimum 35 bytes required: 1 for dest hash + 32 for public key + 2 for MAC)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
let offset = 0;
// Parse destination hash (1 byte)
const destinationHash = (0, hex_1.byteToHex)(payload[0]);
if (options?.includeSegments) {
segments.push({
name: 'Destination Hash',
description: `First byte of destination node public key: 0x${destinationHash}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: destinationHash
});
}
offset += 1;
// Parse sender public key (32 bytes)
const senderPublicKey = (0, hex_1.bytesToHex)(payload.subarray(1, 33));
if (options?.includeSegments) {
segments.push({
name: 'Sender Public Key',
description: `Ed25519 public key of the sender (32 bytes)`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 31,
value: senderPublicKey
});
}
offset += 32;
// Parse cipher MAC (2 bytes)
const cipherMac = (0, hex_1.bytesToHex)(payload.subarray(33, 35));
if (options?.includeSegments) {
segments.push({
name: 'Cipher MAC',
description: `MAC for encrypted data verification (2 bytes)`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 1,
value: cipherMac
});
}
offset += 2;
// Parse ciphertext (remaining bytes)
const ciphertext = (0, hex_1.bytesToHex)(payload.subarray(35));
if (options?.includeSegments && payload.length > 35) {
segments.push({
name: 'Ciphertext',
description: `Encrypted message data (${payload.length - 35} bytes). Contains encrypted plaintext with this structure:
• Timestamp (4 bytes) - send time as unix timestamp
• Sync Timestamp (4 bytes) - room server only, sender's "sync messages SINCE x" timestamp
• Password (remaining bytes) - password for repeater/room`,
startByte: segmentOffset + offset,
endByte: segmentOffset + payload.length - 1,
value: ciphertext
});
}
const result = {
type: enums_1.PayloadType.AnonRequest,
version: enums_1.PayloadVersion.Version1,
isValid: true,
destinationHash,
senderPublicKey,
cipherMac,
ciphertext,
ciphertextLength: payload.length - 35
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
catch (error) {
return {
type: enums_1.PayloadType.AnonRequest,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode AnonRequest payload'],
destinationHash: '',
senderPublicKey: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
}
}
}
exports.AnonRequestPayloadDecoder = AnonRequestPayloadDecoder;
//# sourceMappingURL=anon-request.js.map
@@ -0,0 +1 @@
{"version":3,"file":"anon-request.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/anon-request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
import { ControlPayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class ControlPayloadDecoder {
static decode(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): (ControlPayload & {
segments?: PayloadSegment[];
}) | null;
private static decodeDiscoverReq;
private static decodeDiscoverResp;
private static parseTypeFilter;
private static createErrorPayload;
private static readUint32LE;
}
//# sourceMappingURL=control.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/control.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAyD,MAAM,sBAAsB,CAAC;AAC7G,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAKpD,qBAAa,qBAAqB;IAChC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,CAAC,cAAc,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,CAAC,GAAG,IAAI;IAsB9J,OAAO,CAAC,MAAM,CAAC,iBAAiB;IAoHhC,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAwHjC,OAAO,CAAC,MAAM,CAAC,eAAe;IAS9B,OAAO,CAAC,MAAM,CAAC,kBAAkB;IAgCjC,OAAO,CAAC,MAAM,CAAC,YAAY;CAM5B"}
@@ -0,0 +1,279 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControlPayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
const enum_names_1 = require("../../utils/enum-names");
class ControlPayloadDecoder {
static decode(payload, options) {
try {
if (payload.length < 1) {
return this.createErrorPayload('Control payload too short (minimum 1 byte required)', payload, options);
}
const rawFlags = payload[0];
const subType = rawFlags & 0xF0; // upper 4 bits
switch (subType) {
case enums_1.ControlSubType.NodeDiscoverReq:
return this.decodeDiscoverReq(payload, options);
case enums_1.ControlSubType.NodeDiscoverResp:
return this.decodeDiscoverResp(payload, options);
default:
return this.createErrorPayload(`Unknown control sub-type: 0x${subType.toString(16).padStart(2, '0')}`, payload, options);
}
}
catch (error) {
return this.createErrorPayload(error instanceof Error ? error.message : 'Failed to decode control payload', payload, options);
}
}
static decodeDiscoverReq(payload, options) {
const segments = [];
const segmentOffset = options?.segmentOffset ?? 0;
// Minimum size: flags(1) + type_filter(1) + tag(4) = 6 bytes
if (payload.length < 6) {
const result = {
type: enums_1.PayloadType.Control,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['DISCOVER_REQ payload too short (minimum 6 bytes required)'],
subType: enums_1.ControlSubType.NodeDiscoverReq,
rawFlags: payload[0],
prefixOnly: false,
typeFilter: 0,
typeFilterNames: [],
tag: 0,
since: 0
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid DISCOVER_REQ Data',
description: 'DISCOVER_REQ payload too short (minimum 6 bytes required)',
startByte: segmentOffset,
endByte: segmentOffset + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
let offset = 0;
// Byte 0: flags - upper 4 bits is sub_type (0x8), lowest bit is prefix_only
const rawFlags = payload[offset];
const prefixOnly = (rawFlags & 0x01) !== 0;
if (options?.includeSegments) {
segments.push({
name: 'Flags',
description: `Sub-type: DISCOVER_REQ (0x8) | Prefix Only: ${prefixOnly}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: rawFlags.toString(16).padStart(2, '0').toUpperCase()
});
}
offset += 1;
// Byte 1: type_filter - bit for each ADV_TYPE_*
const typeFilter = payload[offset];
const typeFilterNames = this.parseTypeFilter(typeFilter);
if (options?.includeSegments) {
segments.push({
name: 'Type Filter',
description: `Filter mask: 0b${typeFilter.toString(2).padStart(8, '0')} | Types: ${typeFilterNames.length > 0 ? typeFilterNames.join(', ') : 'None'}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: typeFilter.toString(16).padStart(2, '0').toUpperCase()
});
}
offset += 1;
// Bytes 2-5: tag (uint32, little endian)
const tag = this.readUint32LE(payload, offset);
if (options?.includeSegments) {
segments.push({
name: 'Tag',
description: `Random tag for response matching: 0x${tag.toString(16).padStart(8, '0')}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 3,
value: (0, hex_1.bytesToHex)(payload.slice(offset, offset + 4))
});
}
offset += 4;
// Optional: Bytes 6-9: since (uint32, little endian) - epoch timestamp
let since = 0;
if (payload.length >= offset + 4) {
since = this.readUint32LE(payload, offset);
if (options?.includeSegments) {
const sinceDate = since > 0 ? new Date(since * 1000).toISOString().slice(0, 19) + 'Z' : 'N/A';
segments.push({
name: 'Since',
description: `Filter timestamp: ${since} (${sinceDate})`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 3,
value: (0, hex_1.bytesToHex)(payload.slice(offset, offset + 4))
});
}
}
const result = {
type: enums_1.PayloadType.Control,
version: enums_1.PayloadVersion.Version1,
isValid: true,
subType: enums_1.ControlSubType.NodeDiscoverReq,
rawFlags,
prefixOnly,
typeFilter,
typeFilterNames,
tag,
since
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
static decodeDiscoverResp(payload, options) {
const segments = [];
const segmentOffset = options?.segmentOffset ?? 0;
// Minimum size: flags(1) + snr(1) + tag(4) + pubkey(8 for prefix) = 14 bytes
if (payload.length < 14) {
const result = {
type: enums_1.PayloadType.Control,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['DISCOVER_RESP payload too short (minimum 14 bytes required)'],
subType: enums_1.ControlSubType.NodeDiscoverResp,
rawFlags: payload.length > 0 ? payload[0] : 0,
nodeType: enums_1.DeviceRole.Unknown,
nodeTypeName: 'Unknown',
snr: 0,
tag: 0,
publicKey: '',
publicKeyLength: 0
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid DISCOVER_RESP Data',
description: 'DISCOVER_RESP payload too short (minimum 14 bytes required)',
startByte: segmentOffset,
endByte: segmentOffset + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
let offset = 0;
// Byte 0: flags - upper 4 bits is sub_type (0x9), lower 4 bits is node_type
const rawFlags = payload[offset];
const nodeType = (rawFlags & 0x0F);
const nodeTypeName = (0, enum_names_1.getDeviceRoleName)(nodeType);
if (options?.includeSegments) {
segments.push({
name: 'Flags',
description: `Sub-type: DISCOVER_RESP (0x9) | Node Type: ${nodeTypeName}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: rawFlags.toString(16).padStart(2, '0').toUpperCase()
});
}
offset += 1;
// Byte 1: snr (signed int8, represents SNR * 4)
const snrRaw = payload[offset];
const snrSigned = snrRaw > 127 ? snrRaw - 256 : snrRaw;
const snr = snrSigned / 4.0;
if (options?.includeSegments) {
segments.push({
name: 'SNR',
description: `Inbound SNR: ${snr.toFixed(2)} dB (raw: ${snrRaw}, signed: ${snrSigned})`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: snrRaw.toString(16).padStart(2, '0').toUpperCase()
});
}
offset += 1;
// Bytes 2-5: tag (uint32, little endian) - reflected from request
const tag = this.readUint32LE(payload, offset);
if (options?.includeSegments) {
segments.push({
name: 'Tag',
description: `Reflected tag from request: 0x${tag.toString(16).padStart(8, '0')}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 3,
value: (0, hex_1.bytesToHex)(payload.slice(offset, offset + 4))
});
}
offset += 4;
// Remaining bytes: public key (8 bytes for prefix, 32 bytes for full)
const remainingBytes = payload.length - offset;
const publicKeyLength = remainingBytes;
const publicKeyBytes = payload.slice(offset, offset + publicKeyLength);
const publicKey = (0, hex_1.bytesToHex)(publicKeyBytes);
if (options?.includeSegments) {
const keyType = publicKeyLength === 32 ? 'Full Public Key' : 'Public Key Prefix';
segments.push({
name: keyType,
description: `${keyType} (${publicKeyLength} bytes)`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + publicKeyLength - 1,
value: publicKey
});
}
const result = {
type: enums_1.PayloadType.Control,
version: enums_1.PayloadVersion.Version1,
isValid: true,
subType: enums_1.ControlSubType.NodeDiscoverResp,
rawFlags,
nodeType,
nodeTypeName,
snr,
tag,
publicKey,
publicKeyLength
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
static parseTypeFilter(filter) {
const types = [];
if (filter & (1 << enums_1.DeviceRole.ChatNode))
types.push('Chat');
if (filter & (1 << enums_1.DeviceRole.Repeater))
types.push('Repeater');
if (filter & (1 << enums_1.DeviceRole.RoomServer))
types.push('Room');
if (filter & (1 << enums_1.DeviceRole.Sensor))
types.push('Sensor');
return types;
}
static createErrorPayload(error, payload, options) {
const result = {
type: enums_1.PayloadType.Control,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error],
subType: enums_1.ControlSubType.NodeDiscoverReq,
rawFlags: payload.length > 0 ? payload[0] : 0,
prefixOnly: false,
typeFilter: 0,
typeFilterNames: [],
tag: 0,
since: 0
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid Control Data',
description: error,
startByte: options.segmentOffset ?? 0,
endByte: (options.segmentOffset ?? 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
static readUint32LE(buffer, offset) {
return (buffer[offset] |
(buffer[offset + 1] << 8) |
(buffer[offset + 2] << 16) |
(buffer[offset + 3] << 24)) >>> 0; // >>> 0 to ensure unsigned
}
}
exports.ControlPayloadDecoder = ControlPayloadDecoder;
//# sourceMappingURL=control.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
import { GroupTextPayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
import { DecryptionOptions } from '../../types/crypto';
export declare class GroupTextPayloadDecoder {
static decode(payload: Uint8Array, options?: DecryptionOptions & {
includeSegments?: boolean;
segmentOffset?: number;
}): GroupTextPayload & {
segments?: PayloadSegment[];
} | null;
}
//# sourceMappingURL=group-text.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"group-text.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/group-text.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAIvD,qBAAa,uBAAuB;IAClC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE,iBAAiB,GAAG;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,gBAAgB,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;CAyHnL"}
@@ -0,0 +1,118 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.GroupTextPayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const channel_crypto_1 = require("../../crypto/channel-crypto");
const hex_1 = require("../../utils/hex");
class GroupTextPayloadDecoder {
static decode(payload, options) {
try {
if (payload.length < 3) {
const result = {
type: enums_1.PayloadType.GroupText,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['GroupText payload too short (need at least channel_hash(1) + MAC(2))'],
channelHash: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid GroupText Data',
description: 'GroupText payload too short (minimum 3 bytes required)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
let offset = 0;
// channel hash (1 byte) - first byte of SHA256 of channel's shared key
const channelHash = (0, hex_1.byteToHex)(payload[offset]);
if (options?.includeSegments) {
segments.push({
name: 'Channel Hash',
description: 'First byte of SHA256 of channel\'s shared key',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: channelHash
});
}
offset += 1;
// MAC (2 bytes) - message authentication code
const cipherMac = (0, hex_1.bytesToHex)(payload.subarray(offset, offset + 2));
if (options?.includeSegments) {
segments.push({
name: 'Cipher MAC',
description: 'MAC for encrypted data',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 1,
value: cipherMac
});
}
offset += 2;
// ciphertext (remaining bytes) - encrypted message
const ciphertext = (0, hex_1.bytesToHex)(payload.subarray(offset));
if (options?.includeSegments && payload.length > offset) {
segments.push({
name: 'Ciphertext',
description: 'Encrypted message content (timestamp + flags + message)',
startByte: segmentOffset + offset,
endByte: segmentOffset + payload.length - 1,
value: ciphertext
});
}
const groupText = {
type: enums_1.PayloadType.GroupText,
version: enums_1.PayloadVersion.Version1,
isValid: true,
channelHash,
cipherMac,
ciphertext,
ciphertextLength: payload.length - 3
};
// attempt decryption if key store is provided
if (options?.keyStore && options.keyStore.hasChannelKey(channelHash)) {
// try all possible keys for this hash (handles collisions)
const channelKeys = options.keyStore.getChannelKeys(channelHash);
for (const channelKey of channelKeys) {
const decryptionResult = channel_crypto_1.ChannelCrypto.decryptGroupTextMessage(ciphertext, cipherMac, channelKey);
if (decryptionResult.success && decryptionResult.data) {
groupText.decrypted = {
timestamp: decryptionResult.data.timestamp,
flags: decryptionResult.data.flags,
sender: decryptionResult.data.sender,
message: decryptionResult.data.message
};
break; // stop trying keys once we find one that works
}
}
}
if (options?.includeSegments) {
groupText.segments = segments;
}
return groupText;
}
catch (error) {
return {
type: enums_1.PayloadType.GroupText,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode GroupText payload'],
channelHash: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
}
}
}
exports.GroupTextPayloadDecoder = GroupTextPayloadDecoder;
//# sourceMappingURL=group-text.js.map
@@ -0,0 +1 @@
{"version":3,"file":"group-text.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/group-text.ts"],"names":[],"mappings":";AAAA,mFAAmF;AACnF,cAAc;;;AAId,6CAAgE;AAEhE,gEAA4D;AAC5D,yCAAwD;AAExD,MAAa,uBAAuB;IAClC,MAAM,CAAC,MAAM,CAAC,OAAmB,EAAE,OAAmF;QACpH,IAAI,CAAC;YACH,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAuD;oBACjE,IAAI,EAAE,mBAAW,CAAC,SAAS;oBAC3B,OAAO,EAAE,sBAAc,CAAC,QAAQ;oBAChC,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,CAAC,sEAAsE,CAAC;oBAChF,WAAW,EAAE,EAAE;oBACf,SAAS,EAAE,EAAE;oBACb,UAAU,EAAE,EAAE;oBACd,gBAAgB,EAAE,CAAC;iBACpB,CAAC;gBAEF,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;oBAC7B,MAAM,CAAC,QAAQ,GAAG,CAAC;4BACjB,IAAI,EAAE,wBAAwB;4BAC9B,WAAW,EAAE,wDAAwD;4BACrE,SAAS,EAAE,OAAO,CAAC,aAAa,IAAI,CAAC;4BACrC,OAAO,EAAE,CAAC,OAAO,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC;4BAC1D,KAAK,EAAE,IAAA,gBAAU,EAAC,OAAO,CAAC;yBAC3B,CAAC,CAAC;gBACL,CAAC;gBAED,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,MAAM,QAAQ,GAAqB,EAAE,CAAC;YACtC,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,CAAC,CAAC;YAClD,IAAI,MAAM,GAAG,CAAC,CAAC;YAEf,uEAAuE;YACvE,MAAM,WAAW,GAAG,IAAA,eAAS,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC/C,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,cAAc;oBACpB,WAAW,EAAE,+CAA+C;oBAC5D,SAAS,EAAE,aAAa,GAAG,MAAM;oBACjC,OAAO,EAAE,aAAa,GAAG,MAAM;oBAC/B,KAAK,EAAE,WAAW;iBACnB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,CAAC,CAAC;YAEZ,8CAA8C;YAC9C,MAAM,SAAS,GAAG,IAAA,gBAAU,EAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YACnE,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,YAAY;oBAClB,WAAW,EAAE,wBAAwB;oBACrC,SAAS,EAAE,aAAa,GAAG,MAAM;oBACjC,OAAO,EAAE,aAAa,GAAG,MAAM,GAAG,CAAC;oBACnC,KAAK,EAAE,SAAS;iBACjB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,CAAC,CAAC;YAEZ,mDAAmD;YACnD,MAAM,UAAU,GAAG,IAAA,gBAAU,EAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;YACxD,IAAI,OAAO,EAAE,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;gBACxD,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,YAAY;oBAClB,WAAW,EAAE,yDAAyD;oBACtE,SAAS,EAAE,aAAa,GAAG,MAAM;oBACjC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC;oBAC3C,KAAK,EAAE,UAAU;iBAClB,CAAC,CAAC;YACL,CAAC;YAED,MAAM,SAAS,GAAuD;gBACpE,IAAI,EAAE,mBAAW,CAAC,SAAS;gBAC3B,OAAO,EAAE,sBAAc,CAAC,QAAQ;gBAChC,OAAO,EAAE,IAAI;gBACb,WAAW;gBACX,SAAS;gBACT,UAAU;gBACV,gBAAgB,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC;aACrC,CAAC;YAEF,8CAA8C;YAC9C,IAAI,OAAO,EAAE,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,WAAW,CAAC,EAAE,CAAC;gBACrE,2DAA2D;gBAC3D,MAAM,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;gBAEjE,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;oBACrC,MAAM,gBAAgB,GAAG,8BAAa,CAAC,uBAAuB,CAC5D,UAAU,EACV,SAAS,EACT,UAAU,CACX,CAAC;oBAEF,IAAI,gBAAgB,CAAC,OAAO,IAAI,gBAAgB,CAAC,IAAI,EAAE,CAAC;wBACtD,SAAS,CAAC,SAAS,GAAG;4BACpB,SAAS,EAAE,gBAAgB,CAAC,IAAI,CAAC,SAAS;4BAC1C,KAAK,EAAE,gBAAgB,CAAC,IAAI,CAAC,KAAK;4BAClC,MAAM,EAAE,gBAAgB,CAAC,IAAI,CAAC,MAAM;4BACpC,OAAO,EAAE,gBAAgB,CAAC,IAAI,CAAC,OAAO;yBACvC,CAAC;wBACF,MAAM,CAAC,+CAA+C;oBACxD,CAAC;gBACH,CAAC;YACH,CAAC;YAED,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,SAAS,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAChC,CAAC;YAED,OAAO,SAAS,CAAC;QACnB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,IAAI,EAAE,mBAAW,CAAC,SAAS;gBAC3B,OAAO,EAAE,sBAAc,CAAC,QAAQ;gBAChC,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,oCAAoC,CAAC;gBACvF,WAAW,EAAE,EAAE;gBACf,SAAS,EAAE,EAAE;gBACb,UAAU,EAAE,EAAE;gBACd,gBAAgB,EAAE,CAAC;aACpB,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AA1HD,0DA0HC"}
@@ -0,0 +1,5 @@
import { PathPayload } from '../../types/payloads';
export declare class PathPayloadDecoder {
static decode(payload: Uint8Array): PathPayload | null;
}
//# sourceMappingURL=path.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"path.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/path.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAInD,qBAAa,kBAAkB;IAC7B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,GAAG,WAAW,GAAG,IAAI;CA6FvD"}
@@ -0,0 +1,97 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathPayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
class PathPayloadDecoder {
static decode(payload) {
try {
// Based on MeshCore payloads.md - Path payload structure:
// - path_len (1 byte, encoded: bits 7:6 = hash size selector, bits 5:0 = hop count)
// - path (variable length) - list of node hashes (pathHashSize bytes each)
// - extra_type (1 byte) - bundled payload type
// - extra (rest of data) - bundled payload content
if (payload.length < 2) {
return {
type: enums_1.PayloadType.Path,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['Path payload too short (minimum 2 bytes: path length + extra type)'],
pathLength: 0,
pathHashes: [],
extraType: 0,
extraData: ''
};
}
const pathLenByte = payload[0];
const pathHashSize = (pathLenByte >> 6) + 1;
const pathHopCount = pathLenByte & 63;
const pathByteLength = pathHopCount * pathHashSize;
if (pathHashSize === 4) {
return {
type: enums_1.PayloadType.Path,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['Invalid path length byte: reserved hash size (bits 7:6 = 11)'],
pathLength: 0,
pathHashes: [],
extraType: 0,
extraData: ''
};
}
if (payload.length < 1 + pathByteLength + 1) {
return {
type: enums_1.PayloadType.Path,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [`Path payload too short (need ${1 + pathByteLength + 1} bytes for path length + path + extra type)`],
pathLength: pathHopCount,
...(pathHashSize > 1 ? { pathHashSize } : {}),
pathHashes: [],
extraType: 0,
extraData: ''
};
}
// Parse path hashes (pathHashSize bytes each)
const pathHashes = [];
for (let i = 0; i < pathHopCount; i++) {
const hashStart = 1 + i * pathHashSize;
const hashBytes = payload.subarray(hashStart, hashStart + pathHashSize);
pathHashes.push((0, hex_1.bytesToHex)(hashBytes));
}
// Parse extra type (1 byte after path)
const extraType = payload[1 + pathByteLength];
// Parse extra data (remaining bytes)
let extraData = '';
if (payload.length > 1 + pathByteLength + 1) {
extraData = (0, hex_1.bytesToHex)(payload.subarray(1 + pathByteLength + 1));
}
return {
type: enums_1.PayloadType.Path,
version: enums_1.PayloadVersion.Version1,
isValid: true,
pathLength: pathHopCount,
...(pathHashSize > 1 ? { pathHashSize } : {}),
pathHashes,
extraType,
extraData
};
}
catch (error) {
return {
type: enums_1.PayloadType.Path,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode Path payload'],
pathLength: 0,
pathHashes: [],
extraType: 0,
extraData: ''
};
}
}
}
exports.PathPayloadDecoder = PathPayloadDecoder;
//# sourceMappingURL=path.js.map
@@ -0,0 +1 @@
{"version":3,"file":"path.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
import { RequestPayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class RequestPayloadDecoder {
static decode(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): RequestPayload & {
segments?: PayloadSegment[];
} | null;
}
//# sourceMappingURL=request.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/request.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD,qBAAa,qBAAqB;IAChC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,cAAc,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;CAqI7J"}
@@ -0,0 +1,129 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestPayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
class RequestPayloadDecoder {
static decode(payload, options) {
try {
// Based on MeshCore payloads.md - Request payload structure:
// - destination hash (1 byte)
// - source hash (1 byte)
// - cipher MAC (2 bytes)
// - ciphertext (rest of payload) - contains encrypted timestamp, request type, and request data
if (payload.length < 4) {
const result = {
type: enums_1.PayloadType.Request,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['Request payload too short (minimum 4 bytes: dest hash + source hash + MAC)'],
timestamp: 0,
requestType: enums_1.RequestType.GetStats,
requestData: '',
destinationHash: '',
sourceHash: '',
cipherMac: '',
ciphertext: ''
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid Request Data',
description: 'Request payload too short (minimum 4 bytes required: 1 for dest hash + 1 for source hash + 2 for MAC)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
let offset = 0;
// Parse destination hash (1 byte)
const destinationHash = (0, hex_1.bytesToHex)(payload.subarray(offset, offset + 1));
if (options?.includeSegments) {
segments.push({
name: 'Destination Hash',
description: `First byte of destination node public key: 0x${destinationHash}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: destinationHash
});
}
offset += 1;
// Parse source hash (1 byte)
const sourceHash = (0, hex_1.bytesToHex)(payload.subarray(offset, offset + 1));
if (options?.includeSegments) {
segments.push({
name: 'Source Hash',
description: `First byte of source node public key: 0x${sourceHash}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: sourceHash
});
}
offset += 1;
// Parse cipher MAC (2 bytes)
const cipherMac = (0, hex_1.bytesToHex)(payload.subarray(offset, offset + 2));
if (options?.includeSegments) {
segments.push({
name: 'Cipher MAC',
description: `MAC for encrypted data verification (2 bytes)`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 1,
value: cipherMac
});
}
offset += 2;
// Parse ciphertext (remaining bytes)
const ciphertext = (0, hex_1.bytesToHex)(payload.subarray(offset));
if (options?.includeSegments && payload.length > offset) {
segments.push({
name: 'Ciphertext',
description: `Encrypted message data (${payload.length - offset} bytes). Contains encrypted plaintext with this structure:
• Timestamp (4 bytes) - send time as unix timestamp
• Request Type (1 byte) - type of request (GetStats, GetTelemetryData, etc.)
• Request Data (remaining bytes) - additional request-specific data`,
startByte: segmentOffset + offset,
endByte: segmentOffset + payload.length - 1,
value: ciphertext
});
}
const result = {
type: enums_1.PayloadType.Request,
version: enums_1.PayloadVersion.Version1,
isValid: true,
timestamp: 0, // Encrypted, cannot be parsed without decryption
requestType: enums_1.RequestType.GetStats, // Encrypted, cannot be determined without decryption
requestData: '',
destinationHash,
sourceHash,
cipherMac,
ciphertext
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
catch (error) {
return {
type: enums_1.PayloadType.Request,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode request payload'],
timestamp: 0,
requestType: enums_1.RequestType.GetStats,
requestData: '',
destinationHash: '',
sourceHash: '',
cipherMac: '',
ciphertext: ''
};
}
}
}
exports.RequestPayloadDecoder = RequestPayloadDecoder;
//# sourceMappingURL=request.js.map
@@ -0,0 +1 @@
{"version":3,"file":"request.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/request.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
import { ResponsePayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class ResponsePayloadDecoder {
static decode(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): ResponsePayload & {
segments?: PayloadSegment[];
} | null;
}
//# sourceMappingURL=response.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/response.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD,qBAAa,sBAAsB;IACjC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,eAAe,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;CAuH9J"}
@@ -0,0 +1,120 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.ResponsePayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
class ResponsePayloadDecoder {
static decode(payload, options) {
try {
// Based on MeshCore payloads.md - Response payload structure:
// - destination_hash (1 byte)
// - source_hash (1 byte)
// - cipher_mac (2 bytes)
// - ciphertext (rest of payload)
if (payload.length < 4) {
const result = {
type: enums_1.PayloadType.Response,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['Response payload too short (minimum 4 bytes: dest + source + MAC)'],
destinationHash: '',
sourceHash: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid Response Data',
description: 'Response payload too short (minimum 4 bytes required)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
let offset = 0;
// Destination Hash (1 byte)
const destinationHash = (0, hex_1.byteToHex)(payload[offset]);
if (options?.includeSegments) {
segments.push({
name: 'Destination Hash',
description: 'First byte of destination node public key',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: destinationHash
});
}
offset += 1;
// source hash (1 byte)
const sourceHash = (0, hex_1.byteToHex)(payload[offset]);
if (options?.includeSegments) {
segments.push({
name: 'Source Hash',
description: 'First byte of source node public key',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: sourceHash
});
}
offset += 1;
// cipher MAC (2 bytes)
const cipherMac = (0, hex_1.bytesToHex)(payload.subarray(offset, offset + 2));
if (options?.includeSegments) {
segments.push({
name: 'Cipher MAC',
description: 'MAC for encrypted data in next field',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 1,
value: cipherMac
});
}
offset += 2;
// ciphertext (remaining bytes)
const ciphertext = (0, hex_1.bytesToHex)(payload.subarray(offset));
if (options?.includeSegments && payload.length > offset) {
segments.push({
name: 'Ciphertext',
description: 'Encrypted response data (tag + content)',
startByte: segmentOffset + offset,
endByte: segmentOffset + payload.length - 1,
value: ciphertext
});
}
const result = {
type: enums_1.PayloadType.Response,
version: enums_1.PayloadVersion.Version1,
isValid: true,
destinationHash,
sourceHash,
cipherMac,
ciphertext,
ciphertextLength: payload.length - 4
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
catch (error) {
return {
type: enums_1.PayloadType.Response,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode response payload'],
destinationHash: '',
sourceHash: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
}
}
}
exports.ResponsePayloadDecoder = ResponsePayloadDecoder;
//# sourceMappingURL=response.js.map
@@ -0,0 +1 @@
{"version":3,"file":"response.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/response.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
import { TextMessagePayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class TextMessagePayloadDecoder {
static decode(payload: Uint8Array, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): TextMessagePayload & {
segments?: PayloadSegment[];
} | null;
}
//# sourceMappingURL=text-message.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"text-message.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/text-message.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD,qBAAa,yBAAyB;IACpC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,kBAAkB,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;CAuHjK"}
@@ -0,0 +1,120 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.TextMessagePayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
class TextMessagePayloadDecoder {
static decode(payload, options) {
try {
// Based on MeshCore payloads.md - TextMessage payload structure:
// - destination_hash (1 byte)
// - source_hash (1 byte)
// - cipher_mac (2 bytes)
// - ciphertext (rest of payload)
if (payload.length < 4) {
const result = {
type: enums_1.PayloadType.TextMessage,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['TextMessage payload too short (minimum 4 bytes: dest + source + MAC)'],
destinationHash: '',
sourceHash: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid TextMessage Data',
description: 'TextMessage payload too short (minimum 4 bytes required)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
let offset = 0;
// Destination Hash (1 byte)
const destinationHash = (0, hex_1.byteToHex)(payload[offset]);
if (options?.includeSegments) {
segments.push({
name: 'Destination Hash',
description: 'First byte of destination node public key',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: destinationHash
});
}
offset += 1;
// Source Hash (1 byte)
const sourceHash = (0, hex_1.byteToHex)(payload[offset]);
if (options?.includeSegments) {
segments.push({
name: 'Source Hash',
description: 'First byte of source node public key',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: sourceHash
});
}
offset += 1;
// Cipher MAC (2 bytes)
const cipherMac = (0, hex_1.bytesToHex)(payload.subarray(offset, offset + 2));
if (options?.includeSegments) {
segments.push({
name: 'Cipher MAC',
description: 'MAC for encrypted data in next field',
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 1,
value: cipherMac
});
}
offset += 2;
// Ciphertext (remaining bytes)
const ciphertext = (0, hex_1.bytesToHex)(payload.subarray(offset));
if (options?.includeSegments && payload.length > offset) {
segments.push({
name: 'Ciphertext',
description: 'Encrypted message data (timestamp + message text)',
startByte: segmentOffset + offset,
endByte: segmentOffset + payload.length - 1,
value: ciphertext
});
}
const result = {
type: enums_1.PayloadType.TextMessage,
version: enums_1.PayloadVersion.Version1,
isValid: true,
destinationHash,
sourceHash,
cipherMac,
ciphertext,
ciphertextLength: payload.length - 4
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
catch (error) {
return {
type: enums_1.PayloadType.TextMessage,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode TextMessage payload'],
destinationHash: '',
sourceHash: '',
cipherMac: '',
ciphertext: '',
ciphertextLength: 0
};
}
}
}
exports.TextMessagePayloadDecoder = TextMessagePayloadDecoder;
//# sourceMappingURL=text-message.js.map
@@ -0,0 +1 @@
{"version":3,"file":"text-message.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/text-message.ts"],"names":[],"mappings":";AAAA,mFAAmF;AACnF,cAAc;;;AAId,6CAAgE;AAChE,yCAAwD;AAExD,MAAa,yBAAyB;IACpC,MAAM,CAAC,MAAM,CAAC,OAAmB,EAAE,OAA+D;QAChG,IAAI,CAAC;YACH,iEAAiE;YACjE,8BAA8B;YAC9B,yBAAyB;YACzB,yBAAyB;YACzB,iCAAiC;YAEjC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,MAAM,GAAyD;oBACnE,IAAI,EAAE,mBAAW,CAAC,WAAW;oBAC7B,OAAO,EAAE,sBAAc,CAAC,QAAQ;oBAChC,OAAO,EAAE,KAAK;oBACd,MAAM,EAAE,CAAC,sEAAsE,CAAC;oBAChF,eAAe,EAAE,EAAE;oBACnB,UAAU,EAAE,EAAE;oBACd,SAAS,EAAE,EAAE;oBACb,UAAU,EAAE,EAAE;oBACd,gBAAgB,EAAE,CAAC;iBACpB,CAAC;gBAEF,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;oBAC7B,MAAM,CAAC,QAAQ,GAAG,CAAC;4BACjB,IAAI,EAAE,0BAA0B;4BAChC,WAAW,EAAE,0DAA0D;4BACvE,SAAS,EAAE,OAAO,CAAC,aAAa,IAAI,CAAC;4BACrC,OAAO,EAAE,CAAC,OAAO,CAAC,aAAa,IAAI,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC;4BAC1D,KAAK,EAAE,IAAA,gBAAU,EAAC,OAAO,CAAC;yBAC3B,CAAC,CAAC;gBACL,CAAC;gBAED,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,MAAM,QAAQ,GAAqB,EAAE,CAAC;YACtC,MAAM,aAAa,GAAG,OAAO,EAAE,aAAa,IAAI,CAAC,CAAC;YAClD,IAAI,MAAM,GAAG,CAAC,CAAC;YAEf,4BAA4B;YAC5B,MAAM,eAAe,GAAG,IAAA,eAAS,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YACnD,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,kBAAkB;oBACxB,WAAW,EAAE,2CAA2C;oBACxD,SAAS,EAAE,aAAa,GAAG,MAAM;oBACjC,OAAO,EAAE,aAAa,GAAG,MAAM;oBAC/B,KAAK,EAAE,eAAe;iBACvB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,CAAC,CAAC;YAEZ,uBAAuB;YACvB,MAAM,UAAU,GAAG,IAAA,eAAS,EAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;YAC9C,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,aAAa;oBACnB,WAAW,EAAE,sCAAsC;oBACnD,SAAS,EAAE,aAAa,GAAG,MAAM;oBACjC,OAAO,EAAE,aAAa,GAAG,MAAM;oBAC/B,KAAK,EAAE,UAAU;iBAClB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,CAAC,CAAC;YAEZ,uBAAuB;YACvB,MAAM,SAAS,GAAG,IAAA,gBAAU,EAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YACnE,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,YAAY;oBAClB,WAAW,EAAE,sCAAsC;oBACnD,SAAS,EAAE,aAAa,GAAG,MAAM;oBACjC,OAAO,EAAE,aAAa,GAAG,MAAM,GAAG,CAAC;oBACnC,KAAK,EAAE,SAAS;iBACjB,CAAC,CAAC;YACL,CAAC;YACD,MAAM,IAAI,CAAC,CAAC;YAEZ,+BAA+B;YAC/B,MAAM,UAAU,GAAG,IAAA,gBAAU,EAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;YACxD,IAAI,OAAO,EAAE,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;gBACxD,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,YAAY;oBAClB,WAAW,EAAE,mDAAmD;oBAChE,SAAS,EAAE,aAAa,GAAG,MAAM;oBACjC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC;oBAC3C,KAAK,EAAE,UAAU;iBAClB,CAAC,CAAC;YACL,CAAC;YAED,MAAM,MAAM,GAAyD;gBACnE,IAAI,EAAE,mBAAW,CAAC,WAAW;gBAC7B,OAAO,EAAE,sBAAc,CAAC,QAAQ;gBAChC,OAAO,EAAE,IAAI;gBACb,eAAe;gBACf,UAAU;gBACV,SAAS;gBACT,UAAU;gBACV,gBAAgB,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC;aACrC,CAAC;YAEF,IAAI,OAAO,EAAE,eAAe,EAAE,CAAC;gBAC7B,MAAM,CAAC,QAAQ,GAAG,QAAQ,CAAC;YAC7B,CAAC;YAED,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO;gBACL,IAAI,EAAE,mBAAW,CAAC,WAAW;gBAC7B,OAAO,EAAE,sBAAc,CAAC,QAAQ;gBAChC,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,sCAAsC,CAAC;gBACzF,eAAe,EAAE,EAAE;gBACnB,UAAU,EAAE,EAAE;gBACd,SAAS,EAAE,EAAE;gBACb,UAAU,EAAE,EAAE;gBACd,gBAAgB,EAAE,CAAC;aACpB,CAAC;QACJ,CAAC;IACH,CAAC;CACF;AAxHD,8DAwHC"}
@@ -0,0 +1,12 @@
import { TracePayload } from '../../types/payloads';
import { PayloadSegment } from '../../types/packet';
export declare class TracePayloadDecoder {
static decode(payload: Uint8Array, pathData?: string[] | null, options?: {
includeSegments?: boolean;
segmentOffset?: number;
}): TracePayload & {
segments?: PayloadSegment[];
} | null;
private static readUint32LE;
}
//# sourceMappingURL=trace.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"trace.d.ts","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/trace.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAIpD,qBAAa,mBAAmB;IAC9B,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,YAAY,GAAG;QAAE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAA;KAAE,GAAG,IAAI;IAuItL,OAAO,CAAC,MAAM,CAAC,YAAY;CAM5B"}
@@ -0,0 +1,136 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.TracePayloadDecoder = void 0;
const enums_1 = require("../../types/enums");
const hex_1 = require("../../utils/hex");
class TracePayloadDecoder {
static decode(payload, pathData, options) {
try {
if (payload.length < 9) {
const result = {
type: enums_1.PayloadType.Trace,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: ['Trace payload too short (need at least tag(4) + auth(4) + flags(1))'],
traceTag: '00000000',
authCode: 0,
flags: 0,
pathHashes: []
};
if (options?.includeSegments) {
result.segments = [{
name: 'Invalid Trace Data',
description: 'Trace payload too short (minimum 9 bytes required)',
startByte: options.segmentOffset || 0,
endByte: (options.segmentOffset || 0) + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload)
}];
}
return result;
}
let offset = 0;
const segments = [];
const segmentOffset = options?.segmentOffset || 0;
// Trace Tag (4 bytes) - unique identifier
const traceTagRaw = this.readUint32LE(payload, offset);
const traceTag = (0, hex_1.numberToHex)(traceTagRaw, 8);
if (options?.includeSegments) {
segments.push({
name: 'Trace Tag',
description: `Unique identifier for this trace: 0x${traceTagRaw.toString(16).padStart(8, '0')}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 3,
value: (0, hex_1.bytesToHex)(payload.slice(offset, offset + 4))
});
}
offset += 4;
// Auth Code (4 bytes) - authentication/verification code
const authCode = this.readUint32LE(payload, offset);
if (options?.includeSegments) {
segments.push({
name: 'Auth Code',
description: `Authentication/verification code: ${authCode}`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset + 3,
value: (0, hex_1.bytesToHex)(payload.slice(offset, offset + 4))
});
}
offset += 4;
// Flags (1 byte) - application-defined control flags
const flags = payload[offset];
if (options?.includeSegments) {
segments.push({
name: 'Flags',
description: `Application-defined control flags: 0x${flags.toString(16).padStart(2, '0')} (${flags.toString(2).padStart(8, '0')}b)`,
startByte: segmentOffset + offset,
endByte: segmentOffset + offset,
value: flags.toString(16).padStart(2, '0').toUpperCase()
});
}
offset += 1;
// remaining bytes are path hashes (node hashes in the trace path)
const pathHashes = [];
const pathHashesStart = offset;
while (offset < payload.length) {
pathHashes.push((0, hex_1.byteToHex)(payload[offset]));
offset++;
}
if (options?.includeSegments && pathHashes.length > 0) {
const pathHashesDisplay = pathHashes.join(' ');
segments.push({
name: 'Path Hashes',
description: `Node hashes in trace path: ${pathHashesDisplay}`,
startByte: segmentOffset + pathHashesStart,
endByte: segmentOffset + payload.length - 1,
value: (0, hex_1.bytesToHex)(payload.slice(pathHashesStart))
});
}
// extract SNR values from path field for TRACE packets
let snrValues;
if (pathData && pathData.length > 0) {
snrValues = pathData.map(hexByte => {
const byteValue = parseInt(hexByte, 16);
// convert unsigned byte to signed int8 (SNR values are stored as signed int8 * 4)
const snrSigned = byteValue > 127 ? byteValue - 256 : byteValue;
return snrSigned / 4.0; // convert to dB
});
}
const result = {
type: enums_1.PayloadType.Trace,
version: enums_1.PayloadVersion.Version1,
isValid: true,
traceTag,
authCode,
flags,
pathHashes,
snrValues
};
if (options?.includeSegments) {
result.segments = segments;
}
return result;
}
catch (error) {
return {
type: enums_1.PayloadType.Trace,
version: enums_1.PayloadVersion.Version1,
isValid: false,
errors: [error instanceof Error ? error.message : 'Failed to decode trace payload'],
traceTag: '00000000',
authCode: 0,
flags: 0,
pathHashes: []
};
}
}
static readUint32LE(buffer, offset) {
return buffer[offset] |
(buffer[offset + 1] << 8) |
(buffer[offset + 2] << 16) |
(buffer[offset + 3] << 24);
}
}
exports.TracePayloadDecoder = TracePayloadDecoder;
//# sourceMappingURL=trace.js.map
@@ -0,0 +1 @@
{"version":3,"file":"trace.js","sourceRoot":"","sources":["../../../src/decoder/payload-decoders/trace.ts"],"names":[],"mappings":"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"}
+36
View File
@@ -0,0 +1,36 @@
export { MeshCorePacketDecoder } from './decoder/packet-decoder';
export { MeshCorePacketDecoder as MeshCoreDecoder } from './decoder/packet-decoder';
export type { DecodedPacket, PacketStructure, PacketSegment, PayloadSegment, HeaderBreakdown } from './types/packet';
export type { BasePayload, AdvertPayload, TracePayload, GroupTextPayload, RequestPayload, TextMessagePayload, AnonRequestPayload, AckPayload, PathPayload, ResponsePayload, ControlPayloadBase, ControlDiscoverReqPayload, ControlDiscoverRespPayload, ControlPayload, PayloadData } from './types/payloads';
export type { CryptoKeyStore, DecryptionOptions, DecryptionResult, ValidationResult } from './types/crypto';
export { RouteType, PayloadType, PayloadVersion, DeviceRole, AdvertFlags, RequestType, ControlSubType } from './types/enums';
export { MeshCoreKeyStore } from './crypto/key-manager';
export { ChannelCrypto } from './crypto/channel-crypto';
export { Ed25519SignatureVerifier } from './crypto/ed25519-verifier';
export { hexToBytes, bytesToHex, byteToHex, numberToHex } from './utils/hex';
export { getRouteTypeName, getPayloadTypeName, getPayloadVersionName, getDeviceRoleName, getRequestTypeName, getControlSubTypeName } from './utils/enum-names';
export { createAuthToken, verifyAuthToken, parseAuthToken, decodeAuthTokenPayload } from './utils/auth-token';
export type { AuthTokenPayload, AuthToken } from './utils/auth-token';
import * as AuthTokenUtils from './utils/auth-token';
import { derivePublicKey, validateKeyPair, sign, verify } from './crypto/orlp-ed25519-wasm';
export declare const Utils: {
derivePublicKey: typeof derivePublicKey;
validateKeyPair: typeof validateKeyPair;
sign: typeof sign;
verify: typeof verify;
createAuthToken(payload: AuthTokenUtils.AuthTokenPayload, privateKeyHex: string, publicKeyHex: string): Promise<string>;
verifyAuthToken(token: string, expectedPublicKeyHex?: string): Promise<AuthTokenUtils.AuthTokenPayload | null>;
parseAuthToken(token: string): AuthTokenUtils.AuthToken | null;
decodeAuthTokenPayload(token: string): AuthTokenUtils.AuthTokenPayload | null;
byteToHex(byte: number): string;
bytesToHex(bytes: Uint8Array): string;
numberToHex(num: number, padLength?: number): string;
hexToBytes(hex: string): Uint8Array;
getRouteTypeName(routeType: import("./types/enums").RouteType): string;
getPayloadTypeName(payloadType: import("./types/enums").PayloadType): string;
getPayloadVersionName(version: import("./types/enums").PayloadVersion): string;
getDeviceRoleName(role: import("./types/enums").DeviceRole): string;
getRequestTypeName(requestType: import("./types/enums").RequestType): string;
getControlSubTypeName(subType: import("./types/enums").ControlSubType): string;
};
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,qBAAqB,IAAI,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAGpF,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACrH,YAAY,EACV,WAAW,EACX,aAAa,EACb,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,eAAe,EACf,kBAAkB,EAClB,yBAAyB,EACzB,0BAA0B,EAC1B,cAAc,EACd,WAAW,EACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAG5G,OAAO,EACL,SAAS,EACT,WAAW,EACX,cAAc,EACd,UAAU,EACV,WAAW,EACX,WAAW,EACX,cAAc,EACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AACxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC7E,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,kBAAkB,EAClB,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,eAAe,EACf,eAAe,EACf,cAAc,EACd,sBAAsB,EACvB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAItE,OAAO,KAAK,cAAc,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAC;AAE5F,eAAO,MAAM,KAAK;;;;;;;;;;;;;;;;;;;CAQjB,CAAC"}
+91
View File
@@ -0,0 +1,91 @@
"use strict";
// MeshCore Packet Decoder
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Utils = exports.decodeAuthTokenPayload = exports.parseAuthToken = exports.verifyAuthToken = exports.createAuthToken = exports.getControlSubTypeName = exports.getRequestTypeName = exports.getDeviceRoleName = exports.getPayloadVersionName = exports.getPayloadTypeName = exports.getRouteTypeName = exports.numberToHex = exports.byteToHex = exports.bytesToHex = exports.hexToBytes = exports.Ed25519SignatureVerifier = exports.ChannelCrypto = exports.MeshCoreKeyStore = exports.ControlSubType = exports.RequestType = exports.AdvertFlags = exports.DeviceRole = exports.PayloadVersion = exports.PayloadType = exports.RouteType = exports.MeshCoreDecoder = exports.MeshCorePacketDecoder = void 0;
var packet_decoder_1 = require("./decoder/packet-decoder");
Object.defineProperty(exports, "MeshCorePacketDecoder", { enumerable: true, get: function () { return packet_decoder_1.MeshCorePacketDecoder; } });
var packet_decoder_2 = require("./decoder/packet-decoder");
Object.defineProperty(exports, "MeshCoreDecoder", { enumerable: true, get: function () { return packet_decoder_2.MeshCorePacketDecoder; } });
// Enum exports
var enums_1 = require("./types/enums");
Object.defineProperty(exports, "RouteType", { enumerable: true, get: function () { return enums_1.RouteType; } });
Object.defineProperty(exports, "PayloadType", { enumerable: true, get: function () { return enums_1.PayloadType; } });
Object.defineProperty(exports, "PayloadVersion", { enumerable: true, get: function () { return enums_1.PayloadVersion; } });
Object.defineProperty(exports, "DeviceRole", { enumerable: true, get: function () { return enums_1.DeviceRole; } });
Object.defineProperty(exports, "AdvertFlags", { enumerable: true, get: function () { return enums_1.AdvertFlags; } });
Object.defineProperty(exports, "RequestType", { enumerable: true, get: function () { return enums_1.RequestType; } });
Object.defineProperty(exports, "ControlSubType", { enumerable: true, get: function () { return enums_1.ControlSubType; } });
// Crypto exports
var key_manager_1 = require("./crypto/key-manager");
Object.defineProperty(exports, "MeshCoreKeyStore", { enumerable: true, get: function () { return key_manager_1.MeshCoreKeyStore; } });
var channel_crypto_1 = require("./crypto/channel-crypto");
Object.defineProperty(exports, "ChannelCrypto", { enumerable: true, get: function () { return channel_crypto_1.ChannelCrypto; } });
var ed25519_verifier_1 = require("./crypto/ed25519-verifier");
Object.defineProperty(exports, "Ed25519SignatureVerifier", { enumerable: true, get: function () { return ed25519_verifier_1.Ed25519SignatureVerifier; } });
// Utility exports
var hex_1 = require("./utils/hex");
Object.defineProperty(exports, "hexToBytes", { enumerable: true, get: function () { return hex_1.hexToBytes; } });
Object.defineProperty(exports, "bytesToHex", { enumerable: true, get: function () { return hex_1.bytesToHex; } });
Object.defineProperty(exports, "byteToHex", { enumerable: true, get: function () { return hex_1.byteToHex; } });
Object.defineProperty(exports, "numberToHex", { enumerable: true, get: function () { return hex_1.numberToHex; } });
var enum_names_1 = require("./utils/enum-names");
Object.defineProperty(exports, "getRouteTypeName", { enumerable: true, get: function () { return enum_names_1.getRouteTypeName; } });
Object.defineProperty(exports, "getPayloadTypeName", { enumerable: true, get: function () { return enum_names_1.getPayloadTypeName; } });
Object.defineProperty(exports, "getPayloadVersionName", { enumerable: true, get: function () { return enum_names_1.getPayloadVersionName; } });
Object.defineProperty(exports, "getDeviceRoleName", { enumerable: true, get: function () { return enum_names_1.getDeviceRoleName; } });
Object.defineProperty(exports, "getRequestTypeName", { enumerable: true, get: function () { return enum_names_1.getRequestTypeName; } });
Object.defineProperty(exports, "getControlSubTypeName", { enumerable: true, get: function () { return enum_names_1.getControlSubTypeName; } });
var auth_token_1 = require("./utils/auth-token");
Object.defineProperty(exports, "createAuthToken", { enumerable: true, get: function () { return auth_token_1.createAuthToken; } });
Object.defineProperty(exports, "verifyAuthToken", { enumerable: true, get: function () { return auth_token_1.verifyAuthToken; } });
Object.defineProperty(exports, "parseAuthToken", { enumerable: true, get: function () { return auth_token_1.parseAuthToken; } });
Object.defineProperty(exports, "decodeAuthTokenPayload", { enumerable: true, get: function () { return auth_token_1.decodeAuthTokenPayload; } });
const EnumUtils = __importStar(require("./utils/enum-names"));
const HexUtils = __importStar(require("./utils/hex"));
const AuthTokenUtils = __importStar(require("./utils/auth-token"));
const orlp_ed25519_wasm_1 = require("./crypto/orlp-ed25519-wasm");
exports.Utils = {
...EnumUtils,
...HexUtils,
...AuthTokenUtils,
derivePublicKey: orlp_ed25519_wasm_1.derivePublicKey,
validateKeyPair: orlp_ed25519_wasm_1.validateKeyPair,
sign: orlp_ed25519_wasm_1.sign,
verify: orlp_ed25519_wasm_1.verify
};
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,0BAA0B;AAC1B,mFAAmF;AACnF,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEd,2DAAiE;AAAxD,uHAAA,qBAAqB,OAAA;AAC9B,2DAAoF;AAA3E,iHAAA,qBAAqB,OAAmB;AAuBjD,eAAe;AACf,uCAQuB;AAPrB,kGAAA,SAAS,OAAA;AACT,oGAAA,WAAW,OAAA;AACX,uGAAA,cAAc,OAAA;AACd,mGAAA,UAAU,OAAA;AACV,oGAAA,WAAW,OAAA;AACX,oGAAA,WAAW,OAAA;AACX,uGAAA,cAAc,OAAA;AAGhB,iBAAiB;AACjB,oDAAwD;AAA/C,+GAAA,gBAAgB,OAAA;AACzB,0DAAwD;AAA/C,+GAAA,aAAa,OAAA;AACtB,8DAAqE;AAA5D,4HAAA,wBAAwB,OAAA;AAEjC,kBAAkB;AAClB,mCAA6E;AAApE,iGAAA,UAAU,OAAA;AAAE,iGAAA,UAAU,OAAA;AAAE,gGAAA,SAAS,OAAA;AAAE,kGAAA,WAAW,OAAA;AACvD,iDAO4B;AAN1B,8GAAA,gBAAgB,OAAA;AAChB,gHAAA,kBAAkB,OAAA;AAClB,mHAAA,qBAAqB,OAAA;AACrB,+GAAA,iBAAiB,OAAA;AACjB,gHAAA,kBAAkB,OAAA;AAClB,mHAAA,qBAAqB,OAAA;AAEvB,iDAK4B;AAJ1B,6GAAA,eAAe,OAAA;AACf,6GAAA,eAAe,OAAA;AACf,4GAAA,cAAc,OAAA;AACd,oHAAA,sBAAsB,OAAA;AAIxB,8DAAgD;AAChD,sDAAwC;AACxC,mEAAqD;AACrD,kEAA4F;AAE/E,QAAA,KAAK,GAAG;IACnB,GAAG,SAAS;IACZ,GAAG,QAAQ;IACX,GAAG,cAAc;IACjB,eAAe,EAAf,mCAAe;IACf,eAAe,EAAf,mCAAe;IACf,IAAI,EAAJ,wBAAI;IACJ,MAAM,EAAN,0BAAM;CACP,CAAC"}
+22
View File
@@ -0,0 +1,22 @@
export interface CryptoKeyStore {
nodeKeys: Map<string, string>;
addNodeKey(publicKey: string, privateKey: string): void;
hasChannelKey(channelHash: string): boolean;
hasNodeKey(publicKey: string): boolean;
getChannelKeys(channelHash: string): string[];
}
export interface DecryptionOptions {
keyStore?: CryptoKeyStore;
attemptDecryption?: boolean;
includeRawCiphertext?: boolean;
}
export interface DecryptionResult {
success: boolean;
data?: any;
error?: string;
}
export interface ValidationResult {
isValid: boolean;
errors?: string[];
}
//# sourceMappingURL=crypto.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../../src/types/crypto.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc;IAE7B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAG9B,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAGxD,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC;IAC5C,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB"}
+5
View File
@@ -0,0 +1,5 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/types/crypto.ts"],"names":[],"mappings":";AAAA,mFAAmF;AACnF,cAAc"}
+52
View File
@@ -0,0 +1,52 @@
export declare enum RouteType {
TransportFlood = 0,
Flood = 1,
Direct = 2,
TransportDirect = 3
}
export declare enum PayloadType {
Request = 0,
Response = 1,
TextMessage = 2,
Ack = 3,
Advert = 4,
GroupText = 5,
GroupData = 6,
AnonRequest = 7,
Path = 8,
Trace = 9,
Multipart = 10,
Control = 11,
RawCustom = 15
}
export declare enum ControlSubType {
NodeDiscoverReq = 128,
NodeDiscoverResp = 144
}
export declare enum PayloadVersion {
Version1 = 0,
Version2 = 1,
Version3 = 2,
Version4 = 3
}
export declare enum DeviceRole {
Unknown = 0,
ChatNode = 1,
Repeater = 2,
RoomServer = 3,
Sensor = 4
}
export declare enum AdvertFlags {
HasLocation = 16,
HasFeature1 = 32,
HasFeature2 = 64,
HasName = 128
}
export declare enum RequestType {
GetStats = 1,
Keepalive = 2,// deprecated
GetTelemetryData = 3,
GetMinMaxAvgData = 4,
GetAccessList = 5
}
//# sourceMappingURL=enums.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"enums.d.ts","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":"AAEA,oBAAY,SAAS;IACnB,cAAc,IAAO;IACrB,KAAK,IAAO;IACZ,MAAM,IAAO;IACb,eAAe,IAAO;CACvB;AAED,oBAAY,WAAW;IACrB,OAAO,IAAO;IACd,QAAQ,IAAO;IACf,WAAW,IAAO;IAClB,GAAG,IAAO;IACV,MAAM,IAAO;IACb,SAAS,IAAO;IAChB,SAAS,IAAO;IAChB,WAAW,IAAO;IAClB,IAAI,IAAO;IACX,KAAK,IAAO;IACZ,SAAS,KAAO;IAChB,OAAO,KAAO;IACd,SAAS,KAAO;CACjB;AAGD,oBAAY,cAAc;IACxB,eAAe,MAAO;IACtB,gBAAgB,MAAO;CACxB;AAED,oBAAY,cAAc;IACxB,QAAQ,IAAO;IACf,QAAQ,IAAO;IACf,QAAQ,IAAO;IACf,QAAQ,IAAO;CAChB;AAED,oBAAY,UAAU;IACpB,OAAO,IAAO;IACd,QAAQ,IAAO;IACf,QAAQ,IAAO;IACf,UAAU,IAAO;IACjB,MAAM,IAAO;CACd;AAED,oBAAY,WAAW;IACrB,WAAW,KAAO;IAClB,WAAW,KAAO;IAClB,WAAW,KAAO;IAClB,OAAO,MAAO;CACf;AAED,oBAAY,WAAW;IACrB,QAAQ,IAAO;IACf,SAAS,IAAO,CAAE,aAAa;IAC/B,gBAAgB,IAAO;IACvB,gBAAgB,IAAO;IACvB,aAAa,IAAO;CACrB"}
+64
View File
@@ -0,0 +1,64 @@
"use strict";
// Reference: https://github.com/meshcore-dev/MeshCore/blob/main/docs/packet_structure.md
Object.defineProperty(exports, "__esModule", { value: true });
exports.RequestType = exports.AdvertFlags = exports.DeviceRole = exports.PayloadVersion = exports.ControlSubType = exports.PayloadType = exports.RouteType = void 0;
var RouteType;
(function (RouteType) {
RouteType[RouteType["TransportFlood"] = 0] = "TransportFlood";
RouteType[RouteType["Flood"] = 1] = "Flood";
RouteType[RouteType["Direct"] = 2] = "Direct";
RouteType[RouteType["TransportDirect"] = 3] = "TransportDirect";
})(RouteType || (exports.RouteType = RouteType = {}));
var PayloadType;
(function (PayloadType) {
PayloadType[PayloadType["Request"] = 0] = "Request";
PayloadType[PayloadType["Response"] = 1] = "Response";
PayloadType[PayloadType["TextMessage"] = 2] = "TextMessage";
PayloadType[PayloadType["Ack"] = 3] = "Ack";
PayloadType[PayloadType["Advert"] = 4] = "Advert";
PayloadType[PayloadType["GroupText"] = 5] = "GroupText";
PayloadType[PayloadType["GroupData"] = 6] = "GroupData";
PayloadType[PayloadType["AnonRequest"] = 7] = "AnonRequest";
PayloadType[PayloadType["Path"] = 8] = "Path";
PayloadType[PayloadType["Trace"] = 9] = "Trace";
PayloadType[PayloadType["Multipart"] = 10] = "Multipart";
PayloadType[PayloadType["Control"] = 11] = "Control";
PayloadType[PayloadType["RawCustom"] = 15] = "RawCustom";
})(PayloadType || (exports.PayloadType = PayloadType = {}));
// Control packet sub-types (upper 4 bits of first payload byte)
var ControlSubType;
(function (ControlSubType) {
ControlSubType[ControlSubType["NodeDiscoverReq"] = 128] = "NodeDiscoverReq";
ControlSubType[ControlSubType["NodeDiscoverResp"] = 144] = "NodeDiscoverResp";
})(ControlSubType || (exports.ControlSubType = ControlSubType = {}));
var PayloadVersion;
(function (PayloadVersion) {
PayloadVersion[PayloadVersion["Version1"] = 0] = "Version1";
PayloadVersion[PayloadVersion["Version2"] = 1] = "Version2";
PayloadVersion[PayloadVersion["Version3"] = 2] = "Version3";
PayloadVersion[PayloadVersion["Version4"] = 3] = "Version4";
})(PayloadVersion || (exports.PayloadVersion = PayloadVersion = {}));
var DeviceRole;
(function (DeviceRole) {
DeviceRole[DeviceRole["Unknown"] = 0] = "Unknown";
DeviceRole[DeviceRole["ChatNode"] = 1] = "ChatNode";
DeviceRole[DeviceRole["Repeater"] = 2] = "Repeater";
DeviceRole[DeviceRole["RoomServer"] = 3] = "RoomServer";
DeviceRole[DeviceRole["Sensor"] = 4] = "Sensor";
})(DeviceRole || (exports.DeviceRole = DeviceRole = {}));
var AdvertFlags;
(function (AdvertFlags) {
AdvertFlags[AdvertFlags["HasLocation"] = 16] = "HasLocation";
AdvertFlags[AdvertFlags["HasFeature1"] = 32] = "HasFeature1";
AdvertFlags[AdvertFlags["HasFeature2"] = 64] = "HasFeature2";
AdvertFlags[AdvertFlags["HasName"] = 128] = "HasName";
})(AdvertFlags || (exports.AdvertFlags = AdvertFlags = {}));
var RequestType;
(function (RequestType) {
RequestType[RequestType["GetStats"] = 1] = "GetStats";
RequestType[RequestType["Keepalive"] = 2] = "Keepalive";
RequestType[RequestType["GetTelemetryData"] = 3] = "GetTelemetryData";
RequestType[RequestType["GetMinMaxAvgData"] = 4] = "GetMinMaxAvgData";
RequestType[RequestType["GetAccessList"] = 5] = "GetAccessList";
})(RequestType || (exports.RequestType = RequestType = {}));
//# sourceMappingURL=enums.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../../src/types/enums.ts"],"names":[],"mappings":";AAAA,yFAAyF;;;AAEzF,IAAY,SAKX;AALD,WAAY,SAAS;IACnB,6DAAqB,CAAA;IACrB,2CAAY,CAAA;IACZ,6CAAa,CAAA;IACb,+DAAsB,CAAA;AACxB,CAAC,EALW,SAAS,yBAAT,SAAS,QAKpB;AAED,IAAY,WAcX;AAdD,WAAY,WAAW;IACrB,mDAAc,CAAA;IACd,qDAAe,CAAA;IACf,2DAAkB,CAAA;IAClB,2CAAU,CAAA;IACV,iDAAa,CAAA;IACb,uDAAgB,CAAA;IAChB,uDAAgB,CAAA;IAChB,2DAAkB,CAAA;IAClB,6CAAW,CAAA;IACX,+CAAY,CAAA;IACZ,wDAAgB,CAAA;IAChB,oDAAc,CAAA;IACd,wDAAgB,CAAA;AAClB,CAAC,EAdW,WAAW,2BAAX,WAAW,QActB;AAED,gEAAgE;AAChE,IAAY,cAGX;AAHD,WAAY,cAAc;IACxB,2EAAsB,CAAA;IACtB,6EAAuB,CAAA;AACzB,CAAC,EAHW,cAAc,8BAAd,cAAc,QAGzB;AAED,IAAY,cAKX;AALD,WAAY,cAAc;IACxB,2DAAe,CAAA;IACf,2DAAe,CAAA;IACf,2DAAe,CAAA;IACf,2DAAe,CAAA;AACjB,CAAC,EALW,cAAc,8BAAd,cAAc,QAKzB;AAED,IAAY,UAMX;AAND,WAAY,UAAU;IACpB,iDAAc,CAAA;IACd,mDAAe,CAAA;IACf,mDAAe,CAAA;IACf,uDAAiB,CAAA;IACjB,+CAAa,CAAA;AACf,CAAC,EANW,UAAU,0BAAV,UAAU,QAMrB;AAED,IAAY,WAKX;AALD,WAAY,WAAW;IACrB,4DAAkB,CAAA;IAClB,4DAAkB,CAAA;IAClB,4DAAkB,CAAA;IAClB,qDAAc,CAAA;AAChB,CAAC,EALW,WAAW,2BAAX,WAAW,QAKtB;AAED,IAAY,WAMX;AAND,WAAY,WAAW;IACrB,qDAAe,CAAA;IACf,uDAAgB,CAAA;IAChB,qEAAuB,CAAA;IACvB,qEAAuB,CAAA;IACvB,+DAAoB,CAAA;AACtB,CAAC,EANW,WAAW,2BAAX,WAAW,QAMtB"}
+57
View File
@@ -0,0 +1,57 @@
import { RouteType, PayloadType, PayloadVersion } from './enums';
import { PayloadData } from './payloads';
export interface DecodedPacket {
messageHash: string;
routeType: RouteType;
payloadType: PayloadType;
payloadVersion: PayloadVersion;
transportCodes?: [number, number];
pathLength: number;
pathHashSize?: number;
path: string[] | null;
payload: {
raw: string;
decoded: PayloadData | null;
};
totalBytes: number;
isValid: boolean;
errors?: string[];
}
export interface PacketStructure {
segments: PacketSegment[];
totalBytes: number;
rawHex: string;
messageHash: string;
payload: {
segments: PayloadSegment[];
hex: string;
startByte: number;
type: string;
};
}
export interface PacketSegment {
name: string;
description: string;
startByte: number;
endByte: number;
value: string;
headerBreakdown?: HeaderBreakdown;
}
export interface PayloadSegment {
name: string;
description: string;
startByte: number;
endByte: number;
value: string;
decryptedMessage?: string;
}
export interface HeaderBreakdown {
fullBinary: string;
fields: Array<{
bits: string;
field: string;
value: string;
binary: string;
}>;
}
//# sourceMappingURL=packet.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"packet.d.ts","sourceRoot":"","sources":["../../src/types/packet.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,MAAM,WAAW,aAAa;IAE5B,WAAW,EAAE,MAAM,CAAC;IAGpB,SAAS,EAAE,SAAS,CAAC;IACrB,WAAW,EAAE,WAAW,CAAC;IACzB,cAAc,EAAE,cAAc,CAAC;IAG/B,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAGtB,OAAO,EAAE;QACP,GAAG,EAAE,MAAM,CAAC;QACZ,OAAO,EAAE,WAAW,GAAG,IAAI,CAAC;KAC7B,CAAC;IAGF,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAGD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE;QACP,QAAQ,EAAE,cAAc,EAAE,CAAC;QAC3B,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;KACd,CAAC;CACH;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;KAChB,CAAC,CAAC;CACJ"}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=packet.js.map
@@ -0,0 +1 @@
{"version":3,"file":"packet.js","sourceRoot":"","sources":["../../src/types/packet.ts"],"names":[],"mappings":""}
+128
View File
@@ -0,0 +1,128 @@
import { PayloadType, PayloadVersion, DeviceRole, RequestType, ControlSubType } from './enums';
export interface BasePayload {
type: PayloadType;
version: PayloadVersion;
isValid: boolean;
errors?: string[];
}
export interface AdvertPayload extends BasePayload {
publicKey: string;
timestamp: number;
signature: string;
signatureValid?: boolean;
signatureError?: string;
appData: {
flags: number;
deviceRole: DeviceRole;
hasLocation: boolean;
hasName: boolean;
location?: {
latitude: number;
longitude: number;
};
name?: string;
};
}
export interface TracePayload extends BasePayload {
traceTag: string;
authCode: number;
flags: number;
pathHashes: string[];
snrValues?: number[];
}
export interface GroupTextPayload extends BasePayload {
channelHash: string;
cipherMac: string;
ciphertext: string;
ciphertextLength: number;
decrypted?: {
timestamp: number;
flags: number;
sender?: string;
message: string;
};
}
export interface RequestPayload extends BasePayload {
destinationHash: string;
sourceHash: string;
cipherMac: string;
ciphertext: string;
timestamp: number;
requestType: RequestType;
requestData?: string;
decrypted?: {
timestamp: number;
requestType: RequestType;
requestData?: string;
};
}
export interface TextMessagePayload extends BasePayload {
destinationHash: string;
sourceHash: string;
cipherMac: string;
ciphertext: string;
ciphertextLength: number;
decrypted?: {
timestamp: number;
flags: number;
attempt: number;
message: string;
};
}
export interface AnonRequestPayload extends BasePayload {
destinationHash: string;
senderPublicKey: string;
cipherMac: string;
ciphertext: string;
ciphertextLength: number;
decrypted?: {
timestamp: number;
syncTimestamp?: number;
password: string;
};
}
export interface AckPayload extends BasePayload {
checksum: string;
}
export interface PathPayload extends BasePayload {
pathLength: number;
pathHashSize?: number;
pathHashes: string[];
extraType: number;
extraData: string;
}
export interface ResponsePayload extends BasePayload {
destinationHash: string;
sourceHash: string;
cipherMac: string;
ciphertext: string;
ciphertextLength: number;
decrypted?: {
tag: number;
content: string;
};
}
export interface ControlPayloadBase extends BasePayload {
subType: ControlSubType;
rawFlags: number;
}
export interface ControlDiscoverReqPayload extends ControlPayloadBase {
subType: ControlSubType.NodeDiscoverReq;
prefixOnly: boolean;
typeFilter: number;
typeFilterNames: string[];
tag: number;
since: number;
}
export interface ControlDiscoverRespPayload extends ControlPayloadBase {
subType: ControlSubType.NodeDiscoverResp;
nodeType: DeviceRole;
nodeTypeName: string;
snr: number;
tag: number;
publicKey: string;
publicKeyLength: number;
}
export type ControlPayload = ControlDiscoverReqPayload | ControlDiscoverRespPayload;
export type PayloadData = AdvertPayload | TracePayload | GroupTextPayload | RequestPayload | TextMessagePayload | AnonRequestPayload | AckPayload | PathPayload | ResponsePayload | ControlPayload;
//# sourceMappingURL=payloads.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"payloads.d.ts","sourceRoot":"","sources":["../../src/types/payloads.ts"],"names":[],"mappings":"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"}
+3
View File
@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=payloads.js.map
@@ -0,0 +1 @@
{"version":3,"file":"payloads.js","sourceRoot":"","sources":["../../src/types/payloads.ts"],"names":[],"mappings":""}
@@ -0,0 +1,58 @@
/**
* JWT-style token payload for MeshCore authentication
*/
export interface AuthTokenPayload {
/** Public key of the signer (32 bytes hex) */
publicKey: string;
/** Unix timestamp when token was issued */
iat: number;
/** Unix timestamp when token expires (optional) */
exp?: number;
/** Audience claim (optional) */
aud?: string;
/** Custom claims */
[key: string]: any;
}
/**
* Encoded auth token structure
*/
export interface AuthToken {
/** Base64url-encoded header */
header: string;
/** Base64url-encoded payload */
payload: string;
/** Hex-encoded Ed25519 signature */
signature: string;
}
/**
* Create a signed authentication token
*
* @param payload - Token payload containing claims
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @param publicKeyHex - 32-byte public key in hex format
* @returns JWT-style token string in format: header.payload.signature
*/
export declare function createAuthToken(payload: AuthTokenPayload, privateKeyHex: string, publicKeyHex: string): Promise<string>;
/**
* Verify and decode an authentication token
*
* @param token - JWT-style token string
* @param expectedPublicKeyHex - Expected public key in hex format (optional, will check against payload if provided)
* @returns Decoded payload if valid, null if invalid
*/
export declare function verifyAuthToken(token: string, expectedPublicKeyHex?: string): Promise<AuthTokenPayload | null>;
/**
* Parse a token without verifying (useful for debugging)
*
* @param token - JWT-style token string
* @returns Parsed token structure or null if invalid format
*/
export declare function parseAuthToken(token: string): AuthToken | null;
/**
* Decode token payload without verification (useful for debugging)
*
* @param token - JWT-style token string
* @returns Decoded payload or null if invalid format
*/
export declare function decodeAuthTokenPayload(token: string): AuthTokenPayload | null;
//# sourceMappingURL=auth-token.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"auth-token.d.ts","sourceRoot":"","sources":["../../src/utils/auth-token.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,SAAS,EAAE,MAAM,CAAC;CACnB;AAgDD;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,gBAAgB,EACzB,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CAwCjB;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,oBAAoB,CAAC,EAAE,MAAM,GAC5B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CA0DlC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAe9D;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,gBAAgB,GAAG,IAAI,CAa7E"}
+194
View File
@@ -0,0 +1,194 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAuthToken = createAuthToken;
exports.verifyAuthToken = verifyAuthToken;
exports.parseAuthToken = parseAuthToken;
exports.decodeAuthTokenPayload = decodeAuthTokenPayload;
const orlp_ed25519_wasm_1 = require("../crypto/orlp-ed25519-wasm");
const hex_1 = require("./hex");
/**
* Base64url encode (URL-safe base64 without padding)
*/
function base64urlEncode(data) {
// Convert to base64
let base64 = '';
if (typeof Buffer !== 'undefined') {
// Node.js
base64 = Buffer.from(data).toString('base64');
}
else {
// Browser
const binary = String.fromCharCode(...Array.from(data));
base64 = btoa(binary);
}
// Make URL-safe and remove padding
return base64
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
/**
* Base64url decode
*/
function base64urlDecode(str) {
// Add padding back
let base64 = str.replace(/-/g, '+').replace(/_/g, '/');
while (base64.length % 4) {
base64 += '=';
}
if (typeof Buffer !== 'undefined') {
// Node.js
return new Uint8Array(Buffer.from(base64, 'base64'));
}
else {
// Browser
const binary = atob(base64);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
bytes[i] = binary.charCodeAt(i);
}
return bytes;
}
}
/**
* Create a signed authentication token
*
* @param payload - Token payload containing claims
* @param privateKeyHex - 64-byte private key in hex format (orlp/ed25519 format)
* @param publicKeyHex - 32-byte public key in hex format
* @returns JWT-style token string in format: header.payload.signature
*/
async function createAuthToken(payload, privateKeyHex, publicKeyHex) {
// Create header
const header = {
alg: 'Ed25519',
typ: 'JWT'
};
// Ensure publicKey is in the payload (normalize to uppercase)
if (!payload.publicKey) {
payload.publicKey = publicKeyHex.toUpperCase();
}
else {
payload.publicKey = payload.publicKey.toUpperCase();
}
// Ensure iat is set
if (!payload.iat) {
payload.iat = Math.floor(Date.now() / 1000);
}
// Encode header and payload
const headerJson = JSON.stringify(header);
const payloadJson = JSON.stringify(payload);
const headerBytes = new TextEncoder().encode(headerJson);
const payloadBytes = new TextEncoder().encode(payloadJson);
const headerEncoded = base64urlEncode(headerBytes);
const payloadEncoded = base64urlEncode(payloadBytes);
// Create signing input: header.payload
const signingInput = `${headerEncoded}.${payloadEncoded}`;
const signingInputBytes = new TextEncoder().encode(signingInput);
const signingInputHex = (0, hex_1.bytesToHex)(signingInputBytes);
// Sign the input using the normalized public key from payload
const signatureHex = await (0, orlp_ed25519_wasm_1.sign)(signingInputHex, privateKeyHex, payload.publicKey);
// Return token in JWT format: header.payload.signature
// Note: We use hex for signature instead of base64url for consistency with MeshCore
return `${headerEncoded}.${payloadEncoded}.${signatureHex}`;
}
/**
* Verify and decode an authentication token
*
* @param token - JWT-style token string
* @param expectedPublicKeyHex - Expected public key in hex format (optional, will check against payload if provided)
* @returns Decoded payload if valid, null if invalid
*/
async function verifyAuthToken(token, expectedPublicKeyHex) {
try {
// Parse token
const parts = token.split('.');
if (parts.length !== 3) {
return null;
}
const [headerEncoded, payloadEncoded, signatureHex] = parts;
// Decode header and payload
const headerBytes = base64urlDecode(headerEncoded);
const payloadBytes = base64urlDecode(payloadEncoded);
const headerJson = new TextDecoder().decode(headerBytes);
const payloadJson = new TextDecoder().decode(payloadBytes);
const header = JSON.parse(headerJson);
const payload = JSON.parse(payloadJson);
// Validate header
if (header.alg !== 'Ed25519' || header.typ !== 'JWT') {
return null;
}
// Validate payload has required fields
if (!payload.publicKey || !payload.iat) {
return null;
}
// Check if expected public key matches
if (expectedPublicKeyHex && payload.publicKey.toUpperCase() !== expectedPublicKeyHex.toUpperCase()) {
return null;
}
// Check expiration if present
if (payload.exp) {
const now = Math.floor(Date.now() / 1000);
if (now > payload.exp) {
return null; // Token expired
}
}
// Verify signature
const signingInput = `${headerEncoded}.${payloadEncoded}`;
const signingInputBytes = new TextEncoder().encode(signingInput);
const signingInputHex = (0, hex_1.bytesToHex)(signingInputBytes);
const isValid = await (0, orlp_ed25519_wasm_1.verify)(signatureHex, signingInputHex, payload.publicKey);
if (!isValid) {
return null;
}
return payload;
}
catch (error) {
return null;
}
}
/**
* Parse a token without verifying (useful for debugging)
*
* @param token - JWT-style token string
* @returns Parsed token structure or null if invalid format
*/
function parseAuthToken(token) {
try {
const parts = token.split('.');
if (parts.length !== 3) {
return null;
}
return {
header: parts[0],
payload: parts[1],
signature: parts[2]
};
}
catch (error) {
return null;
}
}
/**
* Decode token payload without verification (useful for debugging)
*
* @param token - JWT-style token string
* @returns Decoded payload or null if invalid format
*/
function decodeAuthTokenPayload(token) {
try {
const parts = token.split('.');
if (parts.length !== 3) {
return null;
}
const payloadBytes = base64urlDecode(parts[1]);
const payloadJson = new TextDecoder().decode(payloadBytes);
return JSON.parse(payloadJson);
}
catch (error) {
return null;
}
}
//# sourceMappingURL=auth-token.js.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,26 @@
import { RouteType, PayloadType, PayloadVersion, DeviceRole, RequestType, ControlSubType } from '../types/enums';
/**
* Get human-readable name for RouteType enum value
*/
export declare function getRouteTypeName(routeType: RouteType): string;
/**
* Get human-readable name for PayloadType enum value
*/
export declare function getPayloadTypeName(payloadType: PayloadType): string;
/**
* Get human-readable name for PayloadVersion enum value
*/
export declare function getPayloadVersionName(version: PayloadVersion): string;
/**
* Get human-readable name for DeviceRole enum value
*/
export declare function getDeviceRoleName(role: DeviceRole): string;
/**
* Get human-readable name for RequestType enum value
*/
export declare function getRequestTypeName(requestType: RequestType): string;
/**
* Get human-readable name for ControlSubType enum value
*/
export declare function getControlSubTypeName(subType: ControlSubType): string;
//# sourceMappingURL=enum-names.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"enum-names.d.ts","sourceRoot":"","sources":["../../src/utils/enum-names.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEjH;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,GAAG,MAAM,CAQ7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CAiBnE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAQrE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAS1D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,WAAW,GAAG,MAAM,CASnE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,CAMrE"}
+93
View File
@@ -0,0 +1,93 @@
"use strict";
// Copyright (c) 2025 Michael Hart: https://github.com/michaelhart/meshcore-decoder
// MIT License
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRouteTypeName = getRouteTypeName;
exports.getPayloadTypeName = getPayloadTypeName;
exports.getPayloadVersionName = getPayloadVersionName;
exports.getDeviceRoleName = getDeviceRoleName;
exports.getRequestTypeName = getRequestTypeName;
exports.getControlSubTypeName = getControlSubTypeName;
const enums_1 = require("../types/enums");
/**
* Get human-readable name for RouteType enum value
*/
function getRouteTypeName(routeType) {
switch (routeType) {
case enums_1.RouteType.Flood: return 'Flood';
case enums_1.RouteType.Direct: return 'Direct';
case enums_1.RouteType.TransportFlood: return 'TransportFlood';
case enums_1.RouteType.TransportDirect: return 'TransportDirect';
default: return `Unknown (${routeType})`;
}
}
/**
* Get human-readable name for PayloadType enum value
*/
function getPayloadTypeName(payloadType) {
switch (payloadType) {
case enums_1.PayloadType.RawCustom: return 'RawCustom';
case enums_1.PayloadType.Trace: return 'Trace';
case enums_1.PayloadType.Advert: return 'Advert';
case enums_1.PayloadType.GroupText: return 'GroupText';
case enums_1.PayloadType.GroupData: return 'GroupData';
case enums_1.PayloadType.Request: return 'Request';
case enums_1.PayloadType.Response: return 'Response';
case enums_1.PayloadType.TextMessage: return 'TextMessage';
case enums_1.PayloadType.AnonRequest: return 'AnonRequest';
case enums_1.PayloadType.Ack: return 'Ack';
case enums_1.PayloadType.Path: return 'Path';
case enums_1.PayloadType.Multipart: return 'Multipart';
case enums_1.PayloadType.Control: return 'Control';
default: return `Unknown (0x${payloadType.toString(16)})`;
}
}
/**
* Get human-readable name for PayloadVersion enum value
*/
function getPayloadVersionName(version) {
switch (version) {
case enums_1.PayloadVersion.Version1: return 'Version 1';
case enums_1.PayloadVersion.Version2: return 'Version 2';
case enums_1.PayloadVersion.Version3: return 'Version 3';
case enums_1.PayloadVersion.Version4: return 'Version 4';
default: return `Unknown (${version})`;
}
}
/**
* Get human-readable name for DeviceRole enum value
*/
function getDeviceRoleName(role) {
switch (role) {
case enums_1.DeviceRole.Unknown: return 'Unknown';
case enums_1.DeviceRole.ChatNode: return 'Chat Node';
case enums_1.DeviceRole.Repeater: return 'Repeater';
case enums_1.DeviceRole.RoomServer: return 'Room Server';
case enums_1.DeviceRole.Sensor: return 'Sensor';
default: return `Unknown (${role})`;
}
}
/**
* Get human-readable name for RequestType enum value
*/
function getRequestTypeName(requestType) {
switch (requestType) {
case enums_1.RequestType.GetStats: return 'Get Stats';
case enums_1.RequestType.Keepalive: return 'Keepalive (deprecated)';
case enums_1.RequestType.GetTelemetryData: return 'Get Telemetry Data';
case enums_1.RequestType.GetMinMaxAvgData: return 'Get Min/Max/Avg Data';
case enums_1.RequestType.GetAccessList: return 'Get Access List';
default: return `Unknown (${requestType})`;
}
}
/**
* Get human-readable name for ControlSubType enum value
*/
function getControlSubTypeName(subType) {
switch (subType) {
case enums_1.ControlSubType.NodeDiscoverReq: return 'Node Discover Request';
case enums_1.ControlSubType.NodeDiscoverResp: return 'Node Discover Response';
default: return `Unknown (0x${subType.toString(16)})`;
}
}
//# sourceMappingURL=enum-names.js.map
@@ -0,0 +1 @@
{"version":3,"file":"enum-names.js","sourceRoot":"","sources":["../../src/utils/enum-names.ts"],"names":[],"mappings":"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"}
+17
View File
@@ -0,0 +1,17 @@
/**
* Convert a single byte to uppercase hex string
*/
export declare function byteToHex(byte: number): string;
/**
* Convert a Uint8Array to uppercase hex string
*/
export declare function bytesToHex(bytes: Uint8Array): string;
/**
* Convert a number to uppercase hex string with specified padding
*/
export declare function numberToHex(num: number, padLength?: number): string;
/**
* Convert hex string to Uint8Array
*/
export declare function hexToBytes(hex: string): Uint8Array;
//# sourceMappingURL=hex.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../../src/utils/hex.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAEpD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,GAAE,MAAU,GAAG,MAAM,CAEtE;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAmBlD"}
+44
View File
@@ -0,0 +1,44 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.byteToHex = byteToHex;
exports.bytesToHex = bytesToHex;
exports.numberToHex = numberToHex;
exports.hexToBytes = hexToBytes;
/**
* Convert a single byte to uppercase hex string
*/
function byteToHex(byte) {
return byte.toString(16).padStart(2, '0').toUpperCase();
}
/**
* Convert a Uint8Array to uppercase hex string
*/
function bytesToHex(bytes) {
return Array.from(bytes).map(byteToHex).join('');
}
/**
* Convert a number to uppercase hex string with specified padding
*/
function numberToHex(num, padLength = 8) {
return (num >>> 0).toString(16).padStart(padLength, '0').toUpperCase();
}
/**
* Convert hex string to Uint8Array
*/
function hexToBytes(hex) {
// Remove any whitespace and convert to uppercase
const cleanHex = hex.replace(/\s/g, '').toUpperCase();
// Validate hex string
if (!/^[0-9A-F]*$/.test(cleanHex)) {
throw new Error(`Invalid hex string: invalid characters at position 0`);
}
if (cleanHex.length % 2 !== 0) {
throw new Error('Invalid hex string: odd length');
}
const bytes = new Uint8Array(cleanHex.length / 2);
for (let i = 0; i < cleanHex.length; i += 2) {
bytes[i / 2] = parseInt(cleanHex.substr(i, 2), 16);
}
return bytes;
}
//# sourceMappingURL=hex.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"hex.js","sourceRoot":"","sources":["../../src/utils/hex.ts"],"names":[],"mappings":";;AAGA,8BAEC;AAKD,gCAEC;AAKD,kCAEC;AAKD,gCAmBC;AA3CD;;GAEG;AACH,SAAgB,SAAS,CAAC,IAAY;IACpC,OAAO,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,SAAgB,UAAU,CAAC,KAAiB;IAC1C,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACnD,CAAC;AAED;;GAEG;AACH,SAAgB,WAAW,CAAC,GAAW,EAAE,YAAoB,CAAC;IAC5D,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;AACzE,CAAC;AAED;;GAEG;AACH,SAAgB,UAAU,CAAC,GAAW;IACpC,iDAAiD;IACjD,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAEtD,sBAAsB;IACtB,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAClC,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;IAC1E,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAClD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5C,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}