node-red-contrib-meshtastic-advanced 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Node-RED Meshtastic Advanced Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,258 @@
1
+ # node-red-contrib-meshtastic-advanced
2
+
3
+ Advanced Node-RED nodes for Meshtastic message encoding, decoding, encryption, and decryption.
4
+
5
+ ## Features
6
+
7
+ - ✅ **Full Encryption Support**: Both channel (PSK) and direct message (PKC) encryption
8
+ - ✅ **All Message Types**: Supports all Meshtastic message types (TEXT, POSITION, TELEMETRY, etc.)
9
+ - ✅ **Modern Architecture**: TypeScript-based with proper error handling
10
+ - ✅ **Secure Key Storage**: Uses Node-RED credentials API for encrypted key storage
11
+ - ✅ **Flexible Design**: Modular nodes that can be chained for custom workflows
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install node-red-contrib-meshtastic-advanced
17
+ ```
18
+
19
+ Or install directly in Node-RED:
20
+ 1. Go to Menu → Manage palette
21
+ 2. Select the Install tab
22
+ 3. Search for `node-red-contrib-meshtastic-advanced`
23
+ 4. Click Install
24
+
25
+ ## Nodes
26
+
27
+ ### 🔓 meshtastic-decrypt
28
+
29
+ Decrypts encrypted Meshtastic packets using either:
30
+ - **Channel encryption (PSK)**: AES-128/256-CTR with pre-shared key
31
+ - **Direct message encryption (PKC)**: x25519 + AES-256-CCM
32
+
33
+ **Configuration:**
34
+ - **Mode**: Auto-detect, Channel (PSK), or Direct Message (PKC)
35
+ - **Channel Key**: Pre-shared key for channel decryption (16 or 32 bytes)
36
+ - **Private Key**: Your x25519 private key for DM decryption
37
+ - **Public Keys**: JSON mapping of sender node numbers to public keys
38
+
39
+ **Input:** Binary ServiceEnvelope with encrypted packet
40
+
41
+ **Output:** ServiceEnvelope with decrypted payload
42
+
43
+ ### 📖 meshtastic-decode
44
+
45
+ Decodes Meshtastic protobuf payloads to readable JSON.
46
+
47
+ **Configuration:**
48
+ - **Output Format**: Flat (all fields at top level) or Nested (organized structure)
49
+ - **Include Raw**: Optionally include raw protobuf bytes
50
+
51
+ **Input:** ServiceEnvelope (binary or object) with decoded payload
52
+
53
+ **Output:** JSON object with decoded message content
54
+
55
+ **Supported Message Types:**
56
+ - TEXT_MESSAGE_APP
57
+ - POSITION_APP
58
+ - NODEINFO_APP
59
+ - TELEMETRY_APP
60
+ - ROUTING_APP
61
+ - ADMIN_APP
62
+ - WAYPOINT_APP
63
+ - NEIGHBORINFO_APP
64
+ - TRACEROUTE_APP
65
+ - And more...
66
+
67
+ ### ✏️ meshtastic-encode
68
+
69
+ Encodes JSON messages to Meshtastic protobuf format.
70
+
71
+ **Configuration:**
72
+ - **Default To**: Destination node (default: broadcast 0xFFFFFFFF)
73
+ - **Default Channel**: Channel index 0-7 (default: 0)
74
+ - **Default Hop Limit**: Max hops 0-7 (default: 3)
75
+ - **Output Type**: ServiceEnvelope (full packet) or Data only
76
+ - **Output Format**: Object or Buffer
77
+
78
+ **Input Example:**
79
+ ```json
80
+ {
81
+ "portNum": "TEXT_MESSAGE_APP",
82
+ "message": "Hello, mesh!",
83
+ "to": 123456,
84
+ "channel": 0,
85
+ "wantAck": false
86
+ }
87
+ ```
88
+
89
+ ### 🔒 meshtastic-encrypt
90
+
91
+ Encrypts Meshtastic payloads using channel PSK or direct message PKC.
92
+
93
+ **Configuration:**
94
+ - **Mode**: Channel (PSK) or Direct Message (PKC)
95
+ - **From Node**: Source node number
96
+ - **Channel Key**: Pre-shared key for channel encryption
97
+ - **My Private Key**: Your x25519 private key for DM encryption
98
+ - **Recipient Public Key**: Recipient's x25519 public key
99
+
100
+ **Input:** Data protobuf or ServiceEnvelope
101
+
102
+ **Output:** Encrypted ServiceEnvelope
103
+
104
+ ## Example Flows
105
+
106
+ ### Receive and Decode Encrypted Messages
107
+
108
+ ```
109
+ [MQTT In] → [meshtastic-decrypt] → [meshtastic-decode] → [Debug]
110
+ ```
111
+
112
+ 1. Subscribe to MQTT topic for incoming Meshtastic messages
113
+ 2. Decrypt using channel key
114
+ 3. Decode to readable JSON
115
+ 4. Display in debug panel
116
+
117
+ ### Send Encrypted Text Messages
118
+
119
+ ```
120
+ [Inject] → [meshtastic-encode] → [meshtastic-encrypt] → [MQTT Out]
121
+ ```
122
+
123
+ 1. Create message payload
124
+ 2. Encode to protobuf
125
+ 3. Encrypt with channel key
126
+ 4. Publish to MQTT
127
+
128
+ ### Multi-Channel Monitoring
129
+
130
+ ```
131
+ [MQTT In] → [Switch] → [meshtastic-decrypt (ch 0)]
132
+ → [meshtastic-decrypt (ch 1)]
133
+ → [meshtastic-decrypt (ch 2)]
134
+ ```
135
+
136
+ Monitor multiple channels with different encryption keys.
137
+
138
+ ## Security Considerations
139
+
140
+ ### Default Key Warning
141
+
142
+ ⚠️ **The default Meshtastic channel key is `AQ==` (base64) which is INSECURE.**
143
+
144
+ Always configure a secure channel key. The nodes will warn you if you use the default key.
145
+
146
+ ### Key Storage
147
+
148
+ All encryption keys are stored using Node-RED's credentials system, which encrypts them at rest. Keys are never exported in flow JSON files.
149
+
150
+ ### Supported Key Formats
151
+
152
+ - **Base64**: Standard base64 encoding (e.g., `AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=`)
153
+ - **Hex**: Hexadecimal encoding (e.g., `0000000000000000000000000000000000000000000000000000000000000000`)
154
+
155
+ ### Key Lengths
156
+
157
+ - **Channel PSK**: 16 bytes (AES-128) or 32 bytes (AES-256)
158
+ - **x25519 Keys**: 32 bytes
159
+
160
+ ## Encryption Details
161
+
162
+ ### Channel Encryption (PSK + AES-CTR)
163
+
164
+ Meshtastic uses AES-CTR for channel encryption with a nonce constructed from:
165
+ - Node number (from field): 32 bits
166
+ - Packet ID: 32 bits
167
+ - Counter: Increments per 16-byte block
168
+
169
+ ### Direct Message Encryption (PKC + AES-CCM)
170
+
171
+ For v2.5+ direct messages:
172
+ 1. Each node has an x25519 key pair
173
+ 2. Shared secret derived using ECDH
174
+ 3. Messages encrypted with AES-256-CCM (provides authentication)
175
+ 4. Random nonce per message
176
+
177
+ ## Development
178
+
179
+ ### Build
180
+
181
+ ```bash
182
+ npm install
183
+ npm run build
184
+ ```
185
+
186
+ ### Run Tests
187
+
188
+ ```bash
189
+ npm test
190
+ ```
191
+
192
+ ### Local Development
193
+
194
+ ```bash
195
+ # Link to local Node-RED
196
+ cd ~/.node-red
197
+ npm link /path/to/node-red-contrib-meshtastic-advanced
198
+
199
+ # Watch mode
200
+ npm run dev
201
+ ```
202
+
203
+ ## Troubleshooting
204
+
205
+ ### "Packet is still encrypted" error
206
+
207
+ Make sure you use the **meshtastic-decrypt** node before the decode node. The decode node only works with already-decrypted packets.
208
+
209
+ ### "Channel decryption requires a channelKey" error
210
+
211
+ Configure the channel key in the decrypt node settings. Make sure it matches the key used by your Meshtastic devices.
212
+
213
+ ### "No public key found for sender" error
214
+
215
+ For DM decryption, you need to configure the sender's public key in the decrypt node's "Public Keys" field.
216
+
217
+ ### "Invalid key length" error
218
+
219
+ Check that your keys are the correct length:
220
+ - Channel keys: 16 bytes (AES-128) or 32 bytes (AES-256)
221
+ - x25519 keys: 32 bytes
222
+
223
+ ## Migration from Old Plugin
224
+
225
+ The old `@meshtastic/node-red-contrib-meshtastic` plugin had limited functionality:
226
+ - Only had a decode node (no decrypt, encode, or encrypt)
227
+ - Decrypt node was non-functional
228
+ - No PKC/DM support
229
+
230
+ This new plugin is a complete rewrite with full functionality.
231
+
232
+ ### Migration Steps
233
+
234
+ 1. Uninstall old plugin: `npm uninstall @meshtastic/node-red-contrib-meshtastic`
235
+ 2. Install new plugin: `npm install node-red-contrib-meshtastic-advanced`
236
+ 3. Update flows to use new node names (meshtastic-decrypt, meshtastic-decode, etc.)
237
+ 4. Configure encryption keys in decrypt/encrypt nodes
238
+
239
+ ## References
240
+
241
+ - [Meshtastic Official Site](https://meshtastic.org/)
242
+ - [Meshtastic Encryption Docs](https://meshtastic.org/docs/overview/encryption/)
243
+ - [Meshtastic Protobufs](https://github.com/meshtastic/protobufs)
244
+ - [Node-RED Documentation](https://nodered.org/docs/)
245
+
246
+ ## License
247
+
248
+ MIT
249
+
250
+ ## Contributing
251
+
252
+ Contributions are welcome! Please open an issue or pull request on GitHub.
253
+
254
+ ## Acknowledgments
255
+
256
+ - [Meshtastic Project](https://github.com/meshtastic) for the amazing mesh networking platform
257
+ - [@noble/curves](https://github.com/paulmillr/noble-curves) for x25519 implementation
258
+ - Node-RED community for the excellent flow-based programming tool
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ const protobufs = require("@meshtastic/protobufs");
3
+ class MeshtasticError extends Error {
4
+ constructor(message, cause) {
5
+ super(message);
6
+ this.cause = cause;
7
+ this.name = "MeshtasticError";
8
+ if (cause) {
9
+ this.stack = `${this.stack}
10
+ Caused by: ${cause.stack}`;
11
+ }
12
+ }
13
+ }
14
+ class MeshtasticDecryptError extends MeshtasticError {
15
+ constructor(message, cause) {
16
+ super(message, cause);
17
+ this.name = "MeshtasticDecryptError";
18
+ }
19
+ }
20
+ class MeshtasticDecodeError extends MeshtasticError {
21
+ constructor(message, cause) {
22
+ super(message, cause);
23
+ this.name = "MeshtasticDecodeError";
24
+ }
25
+ }
26
+ class MeshtasticEncodeError extends MeshtasticError {
27
+ constructor(message, cause) {
28
+ super(message, cause);
29
+ this.name = "MeshtasticEncodeError";
30
+ }
31
+ }
32
+ class MeshtasticEncryptError extends MeshtasticError {
33
+ constructor(message, cause) {
34
+ super(message, cause);
35
+ this.name = "MeshtasticEncryptError";
36
+ }
37
+ }
38
+ class InvalidKeyError extends MeshtasticError {
39
+ constructor(message) {
40
+ super(message);
41
+ this.name = "InvalidKeyError";
42
+ }
43
+ }
44
+ class InvalidNonceError extends MeshtasticError {
45
+ constructor(message) {
46
+ super(message);
47
+ this.name = "InvalidNonceError";
48
+ }
49
+ }
50
+ class ProtobufError extends MeshtasticError {
51
+ constructor(message, cause) {
52
+ super(message, cause);
53
+ this.name = "ProtobufError";
54
+ }
55
+ }
56
+ function ensureBuffer(input) {
57
+ if (Buffer.isBuffer(input)) {
58
+ return input;
59
+ }
60
+ return Buffer.from(input);
61
+ }
62
+ function ensureUint8Array(input) {
63
+ if (input instanceof Uint8Array && !(input instanceof Buffer)) {
64
+ return input;
65
+ }
66
+ return new Uint8Array(input);
67
+ }
68
+ function decodeServiceEnvelope(buffer) {
69
+ try {
70
+ const uint8 = ensureUint8Array(buffer);
71
+ return protobufs.Protobuf.Mqtt.ServiceEnvelope.fromBinary(uint8);
72
+ } catch (error) {
73
+ throw new ProtobufError(
74
+ `Failed to decode ServiceEnvelope: ${error instanceof Error ? error.message : String(error)}`,
75
+ error instanceof Error ? error : void 0
76
+ );
77
+ }
78
+ }
79
+ function decodeData(buffer) {
80
+ try {
81
+ const uint8 = ensureUint8Array(buffer);
82
+ return protobufs.Protobuf.Mesh.Data.fromBinary(uint8);
83
+ } catch (error) {
84
+ throw new ProtobufError(
85
+ `Failed to decode Data: ${error instanceof Error ? error.message : String(error)}`,
86
+ error instanceof Error ? error : void 0
87
+ );
88
+ }
89
+ }
90
+ function decodeMessageByPortNum(portNum, payload) {
91
+ try {
92
+ const uint8 = ensureUint8Array(payload);
93
+ switch (portNum) {
94
+ case protobufs.Protobuf.Portnums.PortNum.TEXT_MESSAGE_APP:
95
+ return new TextDecoder().decode(uint8);
96
+ case protobufs.Protobuf.Portnums.PortNum.POSITION_APP:
97
+ return protobufs.Protobuf.Mesh.Position.fromBinary(uint8);
98
+ case protobufs.Protobuf.Portnums.PortNum.NODEINFO_APP:
99
+ return protobufs.Protobuf.Mesh.User.fromBinary(uint8);
100
+ case protobufs.Protobuf.Portnums.PortNum.TELEMETRY_APP:
101
+ return protobufs.Protobuf.Telemetry.Telemetry.fromBinary(uint8);
102
+ case protobufs.Protobuf.Portnums.PortNum.ROUTING_APP:
103
+ return protobufs.Protobuf.Mesh.Routing.fromBinary(uint8);
104
+ case protobufs.Protobuf.Portnums.PortNum.ADMIN_APP:
105
+ return protobufs.Protobuf.Admin.AdminMessage.fromBinary(uint8);
106
+ case protobufs.Protobuf.Portnums.PortNum.WAYPOINT_APP:
107
+ return protobufs.Protobuf.Mesh.Waypoint.fromBinary(uint8);
108
+ case protobufs.Protobuf.Portnums.PortNum.NEIGHBORINFO_APP:
109
+ return protobufs.Protobuf.Mesh.NeighborInfo.fromBinary(uint8);
110
+ case protobufs.Protobuf.Portnums.PortNum.STORE_FORWARD_APP:
111
+ return protobufs.Protobuf.StoreForward.StoreAndForward.fromBinary(uint8);
112
+ case protobufs.Protobuf.Portnums.PortNum.RANGE_TEST_APP:
113
+ return new TextDecoder("ascii").decode(uint8);
114
+ case protobufs.Protobuf.Portnums.PortNum.REMOTE_HARDWARE_APP:
115
+ return protobufs.Protobuf.RemoteHardware.HardwareMessage.fromBinary(uint8);
116
+ case protobufs.Protobuf.Portnums.PortNum.DETECTION_SENSOR_APP:
117
+ return new TextDecoder().decode(uint8);
118
+ case protobufs.Protobuf.Portnums.PortNum.REPLY_APP:
119
+ return new TextDecoder("ascii").decode(uint8);
120
+ case protobufs.Protobuf.Portnums.PortNum.TRACEROUTE_APP:
121
+ return protobufs.Protobuf.Mesh.RouteDiscovery.fromBinary(uint8);
122
+ case protobufs.Protobuf.Portnums.PortNum.PAXCOUNTER_APP:
123
+ return protobufs.Protobuf.Paxcount.Paxcount.fromBinary(uint8);
124
+ case protobufs.Protobuf.Portnums.PortNum.MAP_REPORT_APP:
125
+ return protobufs.Protobuf.Mqtt.MapReport.fromBinary(uint8);
126
+ case protobufs.Protobuf.Portnums.PortNum.UNKNOWN_APP:
127
+ case protobufs.Protobuf.Portnums.PortNum.IP_TUNNEL_APP:
128
+ case protobufs.Protobuf.Portnums.PortNum.SERIAL_APP:
129
+ case protobufs.Protobuf.Portnums.PortNum.AUDIO_APP:
130
+ case protobufs.Protobuf.Portnums.PortNum.ZPS_APP:
131
+ case protobufs.Protobuf.Portnums.PortNum.SIMULATOR_APP:
132
+ case protobufs.Protobuf.Portnums.PortNum.PRIVATE_APP:
133
+ case protobufs.Protobuf.Portnums.PortNum.ATAK_FORWARDER:
134
+ case protobufs.Protobuf.Portnums.PortNum.TEXT_MESSAGE_COMPRESSED_APP:
135
+ default:
136
+ return {
137
+ raw: Buffer.from(uint8),
138
+ portNum,
139
+ note: `Unsupported or binary message type: ${getPortNumName(portNum)}`
140
+ };
141
+ }
142
+ } catch (error) {
143
+ throw new MeshtasticDecodeError(
144
+ `Failed to decode message for portNum ${portNum}: ${error instanceof Error ? error.message : String(error)}`,
145
+ error instanceof Error ? error : void 0
146
+ );
147
+ }
148
+ }
149
+ function getPortNumName(portNum) {
150
+ const name = protobufs.Protobuf.Portnums.PortNum[portNum];
151
+ return name || `UNKNOWN(${portNum})`;
152
+ }
153
+ exports.InvalidKeyError = InvalidKeyError;
154
+ exports.InvalidNonceError = InvalidNonceError;
155
+ exports.MeshtasticDecodeError = MeshtasticDecodeError;
156
+ exports.MeshtasticDecryptError = MeshtasticDecryptError;
157
+ exports.MeshtasticEncodeError = MeshtasticEncodeError;
158
+ exports.MeshtasticEncryptError = MeshtasticEncryptError;
159
+ exports.MeshtasticError = MeshtasticError;
160
+ exports.ProtobufError = ProtobufError;
161
+ exports.decodeData = decodeData;
162
+ exports.decodeMessageByPortNum = decodeMessageByPortNum;
163
+ exports.decodeServiceEnvelope = decodeServiceEnvelope;
164
+ exports.ensureBuffer = ensureBuffer;
165
+ exports.getPortNumName = getPortNumName;
166
+ //# sourceMappingURL=decoder-BvBAtm2U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decoder-BvBAtm2U.js","sources":["../src/lib/utils/error-types.ts","../src/lib/utils/buffer-utils.ts","../src/lib/protobuf/decoder.ts"],"sourcesContent":["/**\n * Custom error types for Meshtastic operations\n */\n\nexport class MeshtasticError extends Error {\n constructor(message: string, public cause?: Error) {\n super(message);\n this.name = 'MeshtasticError';\n if (cause) {\n this.stack = `${this.stack}\\nCaused by: ${cause.stack}`;\n }\n }\n}\n\nexport class MeshtasticDecryptError extends MeshtasticError {\n constructor(message: string, cause?: Error) {\n super(message, cause);\n this.name = 'MeshtasticDecryptError';\n }\n}\n\nexport class MeshtasticDecodeError extends MeshtasticError {\n constructor(message: string, cause?: Error) {\n super(message, cause);\n this.name = 'MeshtasticDecodeError';\n }\n}\n\nexport class MeshtasticEncodeError extends MeshtasticError {\n constructor(message: string, cause?: Error) {\n super(message, cause);\n this.name = 'MeshtasticEncodeError';\n }\n}\n\nexport class MeshtasticEncryptError extends MeshtasticError {\n constructor(message: string, cause?: Error) {\n super(message, cause);\n this.name = 'MeshtasticEncryptError';\n }\n}\n\nexport class InvalidKeyError extends MeshtasticError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidKeyError';\n }\n}\n\nexport class InvalidNonceError extends MeshtasticError {\n constructor(message: string) {\n super(message);\n this.name = 'InvalidNonceError';\n }\n}\n\nexport class ProtobufError extends MeshtasticError {\n constructor(message: string, cause?: Error) {\n super(message, cause);\n this.name = 'ProtobufError';\n }\n}\n","/**\n * Buffer manipulation utilities\n */\n\n/**\n * Convert a Uint8Array to Buffer\n * @param uint8Array - Uint8Array to convert\n * @returns Buffer\n */\nexport function uint8ArrayToBuffer(uint8Array: Uint8Array): Buffer {\n return Buffer.from(uint8Array);\n}\n\n/**\n * Convert a Buffer to Uint8Array\n * @param buffer - Buffer to convert\n * @returns Uint8Array\n */\nexport function bufferToUint8Array(buffer: Buffer): Uint8Array {\n return new Uint8Array(buffer);\n}\n\n/**\n * Check if input is a Buffer or Uint8Array\n * @param input - Input to check\n * @returns true if input is Buffer or Uint8Array\n */\nexport function isBufferLike(input: any): input is Buffer | Uint8Array {\n return Buffer.isBuffer(input) || input instanceof Uint8Array;\n}\n\n/**\n * Ensure input is a Buffer (convert if needed)\n * @param input - Buffer or Uint8Array\n * @returns Buffer\n */\nexport function ensureBuffer(input: Buffer | Uint8Array): Buffer {\n if (Buffer.isBuffer(input)) {\n return input;\n }\n return Buffer.from(input);\n}\n\n/**\n * Ensure input is a Uint8Array (convert if needed)\n * @param input - Buffer or Uint8Array\n * @returns Uint8Array\n */\nexport function ensureUint8Array(input: Buffer | Uint8Array): Uint8Array {\n if (input instanceof Uint8Array && !(input instanceof Buffer)) {\n return input;\n }\n return new Uint8Array(input);\n}\n\n/**\n * Convert a hex string to Buffer\n * @param hex - Hex string\n * @returns Buffer\n */\nexport function hexToBuffer(hex: string): Buffer {\n return Buffer.from(hex, 'hex');\n}\n\n/**\n * Convert a base64 string to Buffer\n * @param base64 - Base64 string\n * @returns Buffer\n */\nexport function base64ToBuffer(base64: string): Buffer {\n return Buffer.from(base64, 'base64');\n}\n\n/**\n * Convert a Buffer to hex string\n * @param buffer - Buffer\n * @returns Hex string\n */\nexport function bufferToHex(buffer: Buffer): string {\n return buffer.toString('hex');\n}\n\n/**\n * Convert a Buffer to base64 string\n * @param buffer - Buffer\n * @returns Base64 string\n */\nexport function bufferToBase64(buffer: Buffer): string {\n return buffer.toString('base64');\n}\n\n/**\n * Check if two buffers are equal\n * @param a - First buffer\n * @param b - Second buffer\n * @returns true if buffers are equal\n */\nexport function buffersEqual(a: Buffer, b: Buffer): boolean {\n return a.equals(b);\n}\n","/**\n * Protobuf decoding utilities for Meshtastic messages\n */\n\nimport { Protobuf } from '@meshtastic/protobufs';\nimport { ProtobufError, MeshtasticDecodeError } from '../utils/error-types';\nimport { ensureUint8Array } from '../utils/buffer-utils';\n\n/**\n * Decode a ServiceEnvelope from binary data\n * @param buffer - Binary data containing ServiceEnvelope\n * @returns Decoded ServiceEnvelope\n * @throws {ProtobufError} If decoding fails\n */\nexport function decodeServiceEnvelope(buffer: Buffer | Uint8Array): Protobuf.Mqtt.ServiceEnvelope {\n try {\n const uint8 = ensureUint8Array(buffer);\n return Protobuf.Mqtt.ServiceEnvelope.fromBinary(uint8);\n } catch (error) {\n throw new ProtobufError(\n `Failed to decode ServiceEnvelope: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Decode a Data protobuf from binary data\n * @param buffer - Binary data containing Data message\n * @returns Decoded Data\n * @throws {ProtobufError} If decoding fails\n */\nexport function decodeData(buffer: Buffer | Uint8Array): Protobuf.Mesh.Data {\n try {\n const uint8 = ensureUint8Array(buffer);\n return Protobuf.Mesh.Data.fromBinary(uint8);\n } catch (error) {\n throw new ProtobufError(\n `Failed to decode Data: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Decode a message payload based on its port number\n * @param portNum - Port number (message type)\n * @param payload - Binary payload data\n * @returns Decoded message (type depends on portNum)\n * @throws {MeshtasticDecodeError} If decoding fails\n */\nexport function decodeMessageByPortNum(\n portNum: Protobuf.Portnums.PortNum | number,\n payload: Uint8Array | Buffer\n): any {\n try {\n const uint8 = ensureUint8Array(payload);\n\n switch (portNum) {\n case Protobuf.Portnums.PortNum.TEXT_MESSAGE_APP:\n return new TextDecoder().decode(uint8);\n\n case Protobuf.Portnums.PortNum.POSITION_APP:\n return Protobuf.Mesh.Position.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.NODEINFO_APP:\n return Protobuf.Mesh.User.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.TELEMETRY_APP:\n return Protobuf.Telemetry.Telemetry.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.ROUTING_APP:\n return Protobuf.Mesh.Routing.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.ADMIN_APP:\n return Protobuf.Admin.AdminMessage.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.WAYPOINT_APP:\n return Protobuf.Mesh.Waypoint.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.NEIGHBORINFO_APP:\n return Protobuf.Mesh.NeighborInfo.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.STORE_FORWARD_APP:\n return Protobuf.StoreForward.StoreAndForward.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.RANGE_TEST_APP:\n return new TextDecoder('ascii').decode(uint8);\n\n case Protobuf.Portnums.PortNum.REMOTE_HARDWARE_APP:\n return Protobuf.RemoteHardware.HardwareMessage.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.DETECTION_SENSOR_APP:\n return new TextDecoder().decode(uint8);\n\n case Protobuf.Portnums.PortNum.REPLY_APP:\n return new TextDecoder('ascii').decode(uint8);\n\n case Protobuf.Portnums.PortNum.TRACEROUTE_APP:\n return Protobuf.Mesh.RouteDiscovery.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.PAXCOUNTER_APP:\n return Protobuf.Paxcount.Paxcount.fromBinary(uint8);\n\n case Protobuf.Portnums.PortNum.MAP_REPORT_APP:\n return Protobuf.Mqtt.MapReport.fromBinary(uint8);\n\n // For unknown or unsupported message types, return raw bytes\n case Protobuf.Portnums.PortNum.UNKNOWN_APP:\n case Protobuf.Portnums.PortNum.IP_TUNNEL_APP:\n case Protobuf.Portnums.PortNum.SERIAL_APP:\n case Protobuf.Portnums.PortNum.AUDIO_APP:\n case Protobuf.Portnums.PortNum.ZPS_APP:\n case Protobuf.Portnums.PortNum.SIMULATOR_APP:\n case Protobuf.Portnums.PortNum.PRIVATE_APP:\n case Protobuf.Portnums.PortNum.ATAK_FORWARDER:\n case Protobuf.Portnums.PortNum.TEXT_MESSAGE_COMPRESSED_APP:\n default:\n return {\n raw: Buffer.from(uint8),\n portNum,\n note: `Unsupported or binary message type: ${getPortNumName(portNum)}`\n };\n }\n } catch (error) {\n throw new MeshtasticDecodeError(\n `Failed to decode message for portNum ${portNum}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Get the name of a port number\n * @param portNum - Port number\n * @returns Port number name (e.g., \"TEXT_MESSAGE_APP\")\n */\nexport function getPortNumName(portNum: Protobuf.Portnums.PortNum | number): string {\n const name = Protobuf.Portnums.PortNum[portNum];\n return name || `UNKNOWN(${portNum})`;\n}\n\n/**\n * Format a ServiceEnvelope as a JSON object for output\n * @param envelope - ServiceEnvelope to format\n * @param decodePayload - Whether to decode the payload (default: true)\n * @returns Formatted JSON object\n */\nexport function formatServiceEnvelopeAsJson(\n envelope: Protobuf.Mqtt.ServiceEnvelope,\n decodePayload: boolean = true\n): any {\n const packet = envelope.packet;\n\n if (!packet) {\n return {\n channelId: envelope.channelId,\n gatewayId: envelope.gatewayId,\n error: 'No packet in ServiceEnvelope'\n };\n }\n\n const result: any = {\n channelId: envelope.channelId,\n gatewayId: envelope.gatewayId,\n from: packet.from,\n to: packet.to,\n channel: packet.channel,\n packetId: packet.id,\n rxTime: packet.rxTime,\n rxSnr: packet.rxSnr,\n rxRssi: packet.rxRssi,\n hopLimit: packet.hopLimit,\n wantAck: packet.wantAck,\n priority: packet.priority,\n hopStart: packet.hopStart,\n };\n\n // Handle payload based on variant type\n if (packet.payloadVariant.case === 'encrypted') {\n result.encrypted = true;\n result.encryptedData = Buffer.from(packet.payloadVariant.value).toString('base64');\n } else if (packet.payloadVariant.case === 'decoded') {\n const decoded = packet.payloadVariant.value;\n result.encrypted = false;\n result.portNum = getPortNumName(decoded.portnum);\n result.portNumValue = decoded.portnum;\n result.wantResponse = decoded.wantResponse;\n result.requestId = decoded.requestId;\n result.emoji = decoded.emoji;\n\n if (decodePayload && decoded.payload && decoded.payload.length > 0) {\n try {\n result.payload = decodeMessageByPortNum(decoded.portnum, decoded.payload);\n } catch (error) {\n result.payload = {\n raw: Buffer.from(decoded.payload).toString('base64'),\n error: error instanceof Error ? error.message : String(error)\n };\n }\n } else {\n result.payload = decoded.payload ? Buffer.from(decoded.payload).toString('base64') : null;\n }\n }\n\n return result;\n}\n"],"names":["Protobuf"],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ const protobufs = require("@meshtastic/protobufs");
3
+ const decoder = require("./decoder-BvBAtm2U.js");
4
+ function encodeMessageByPortNum(portNum, message) {
5
+ try {
6
+ switch (portNum) {
7
+ case protobufs.Protobuf.Portnums.PortNum.TEXT_MESSAGE_APP:
8
+ if (typeof message !== "string") {
9
+ throw new Error("TEXT_MESSAGE_APP requires a string message");
10
+ }
11
+ return new TextEncoder().encode(message);
12
+ case protobufs.Protobuf.Portnums.PortNum.POSITION_APP:
13
+ return protobufs.Protobuf.Mesh.Position.toBinary(message);
14
+ case protobufs.Protobuf.Portnums.PortNum.NODEINFO_APP:
15
+ return protobufs.Protobuf.Mesh.User.toBinary(message);
16
+ case protobufs.Protobuf.Portnums.PortNum.TELEMETRY_APP:
17
+ return protobufs.Protobuf.Telemetry.Telemetry.toBinary(message);
18
+ case protobufs.Protobuf.Portnums.PortNum.ROUTING_APP:
19
+ return protobufs.Protobuf.Mesh.Routing.toBinary(message);
20
+ case protobufs.Protobuf.Portnums.PortNum.ADMIN_APP:
21
+ return protobufs.Protobuf.Admin.AdminMessage.toBinary(message);
22
+ case protobufs.Protobuf.Portnums.PortNum.WAYPOINT_APP:
23
+ return protobufs.Protobuf.Mesh.Waypoint.toBinary(message);
24
+ case protobufs.Protobuf.Portnums.PortNum.NEIGHBORINFO_APP:
25
+ return protobufs.Protobuf.Mesh.NeighborInfo.toBinary(message);
26
+ case protobufs.Protobuf.Portnums.PortNum.STORE_FORWARD_APP:
27
+ return protobufs.Protobuf.StoreForward.StoreAndForward.toBinary(message);
28
+ case protobufs.Protobuf.Portnums.PortNum.RANGE_TEST_APP:
29
+ case protobufs.Protobuf.Portnums.PortNum.DETECTION_SENSOR_APP:
30
+ case protobufs.Protobuf.Portnums.PortNum.REPLY_APP:
31
+ if (typeof message !== "string") {
32
+ throw new Error(`${portNum} requires a string message`);
33
+ }
34
+ return new TextEncoder().encode(message);
35
+ case protobufs.Protobuf.Portnums.PortNum.REMOTE_HARDWARE_APP:
36
+ return protobufs.Protobuf.RemoteHardware.HardwareMessage.toBinary(message);
37
+ case protobufs.Protobuf.Portnums.PortNum.TRACEROUTE_APP:
38
+ return protobufs.Protobuf.Mesh.RouteDiscovery.toBinary(message);
39
+ case protobufs.Protobuf.Portnums.PortNum.PAXCOUNTER_APP:
40
+ return protobufs.Protobuf.Paxcount.Paxcount.toBinary(message);
41
+ case protobufs.Protobuf.Portnums.PortNum.MAP_REPORT_APP:
42
+ return protobufs.Protobuf.Mqtt.MapReport.toBinary(message);
43
+ default:
44
+ throw new Error(`Encoding not supported for portNum: ${portNum}`);
45
+ }
46
+ } catch (error) {
47
+ throw new decoder.MeshtasticEncodeError(
48
+ `Failed to encode message for portNum ${portNum}: ${error instanceof Error ? error.message : String(error)}`,
49
+ error instanceof Error ? error : void 0
50
+ );
51
+ }
52
+ }
53
+ function createDataPayload(portNum, payload, options) {
54
+ return {
55
+ portnum: portNum,
56
+ payload,
57
+ wantResponse: (options == null ? void 0 : options.wantResponse) ?? false,
58
+ requestId: (options == null ? void 0 : options.requestId) ?? 0,
59
+ emoji: (options == null ? void 0 : options.emoji) ?? 0
60
+ };
61
+ }
62
+ function createServiceEnvelope(data, from, to = 4294967295, channel = 0, packetId, options) {
63
+ return {
64
+ packet: {
65
+ from,
66
+ to,
67
+ channel,
68
+ id: packetId ?? 0,
69
+ rxTime: 0,
70
+ rxSnr: 0,
71
+ rxRssi: 0,
72
+ hopLimit: (options == null ? void 0 : options.hopLimit) ?? 3,
73
+ wantAck: (options == null ? void 0 : options.wantAck) ?? false,
74
+ priority: (options == null ? void 0 : options.priority) ?? protobufs.Protobuf.Mesh.MeshPacket_Priority.UNSET,
75
+ hopStart: (options == null ? void 0 : options.hopLimit) ?? 3,
76
+ payloadVariant: {
77
+ case: "decoded",
78
+ value: data
79
+ }
80
+ },
81
+ channelId: (options == null ? void 0 : options.channelId) ?? "",
82
+ gatewayId: (options == null ? void 0 : options.gatewayId) ?? ""
83
+ };
84
+ }
85
+ function encodeServiceEnvelope(envelope) {
86
+ try {
87
+ const binary = protobufs.Protobuf.Mqtt.ServiceEnvelope.toBinary(envelope);
88
+ return decoder.ensureBuffer(binary);
89
+ } catch (error) {
90
+ throw new decoder.ProtobufError(
91
+ `Failed to encode ServiceEnvelope: ${error instanceof Error ? error.message : String(error)}`,
92
+ error instanceof Error ? error : void 0
93
+ );
94
+ }
95
+ }
96
+ function encodeData(data) {
97
+ try {
98
+ const binary = protobufs.Protobuf.Mesh.Data.toBinary(data);
99
+ return decoder.ensureBuffer(binary);
100
+ } catch (error) {
101
+ throw new decoder.ProtobufError(
102
+ `Failed to encode Data: ${error instanceof Error ? error.message : String(error)}`,
103
+ error instanceof Error ? error : void 0
104
+ );
105
+ }
106
+ }
107
+ function parsePortNum(portNum) {
108
+ if (typeof portNum === "number") {
109
+ if (portNum in protobufs.Protobuf.Portnums.PortNum) {
110
+ return portNum;
111
+ }
112
+ throw new decoder.MeshtasticEncodeError(`Invalid port number: ${portNum}`);
113
+ }
114
+ const enumValue = protobufs.Protobuf.Portnums.PortNum[portNum];
115
+ if (enumValue === void 0) {
116
+ throw new decoder.MeshtasticEncodeError(`Unknown port number name: ${portNum}`);
117
+ }
118
+ return enumValue;
119
+ }
120
+ exports.createDataPayload = createDataPayload;
121
+ exports.createServiceEnvelope = createServiceEnvelope;
122
+ exports.encodeData = encodeData;
123
+ exports.encodeMessageByPortNum = encodeMessageByPortNum;
124
+ exports.encodeServiceEnvelope = encodeServiceEnvelope;
125
+ exports.parsePortNum = parsePortNum;
126
+ //# sourceMappingURL=encoder-DU3wTIEA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encoder-DU3wTIEA.js","sources":["../src/lib/protobuf/encoder.ts"],"sourcesContent":["/**\n * Protobuf encoding utilities for Meshtastic messages\n */\n\nimport { Protobuf } from '@meshtastic/protobufs';\nimport { MeshtasticEncodeError, ProtobufError } from '../utils/error-types';\nimport { ensureBuffer } from '../utils/buffer-utils';\n\n/**\n * Encode a message payload based on its port number\n * @param portNum - Port number (message type)\n * @param message - Message data to encode\n * @returns Encoded binary payload\n * @throws {MeshtasticEncodeError} If encoding fails\n */\nexport function encodeMessageByPortNum(\n portNum: Protobuf.Portnums.PortNum | number,\n message: any\n): Uint8Array {\n try {\n switch (portNum) {\n case Protobuf.Portnums.PortNum.TEXT_MESSAGE_APP:\n // Encode string as UTF-8 bytes\n if (typeof message !== 'string') {\n throw new Error('TEXT_MESSAGE_APP requires a string message');\n }\n return new TextEncoder().encode(message);\n\n case Protobuf.Portnums.PortNum.POSITION_APP:\n return Protobuf.Mesh.Position.toBinary(message as Protobuf.Mesh.Position);\n\n case Protobuf.Portnums.PortNum.NODEINFO_APP:\n return Protobuf.Mesh.User.toBinary(message as Protobuf.Mesh.User);\n\n case Protobuf.Portnums.PortNum.TELEMETRY_APP:\n return Protobuf.Telemetry.Telemetry.toBinary(message as Protobuf.Telemetry.Telemetry);\n\n case Protobuf.Portnums.PortNum.ROUTING_APP:\n return Protobuf.Mesh.Routing.toBinary(message as Protobuf.Mesh.Routing);\n\n case Protobuf.Portnums.PortNum.ADMIN_APP:\n return Protobuf.Admin.AdminMessage.toBinary(message as Protobuf.Admin.AdminMessage);\n\n case Protobuf.Portnums.PortNum.WAYPOINT_APP:\n return Protobuf.Mesh.Waypoint.toBinary(message as Protobuf.Mesh.Waypoint);\n\n case Protobuf.Portnums.PortNum.NEIGHBORINFO_APP:\n return Protobuf.Mesh.NeighborInfo.toBinary(message as Protobuf.Mesh.NeighborInfo);\n\n case Protobuf.Portnums.PortNum.STORE_FORWARD_APP:\n return Protobuf.StoreForward.StoreAndForward.toBinary(message as Protobuf.StoreForward.StoreAndForward);\n\n case Protobuf.Portnums.PortNum.RANGE_TEST_APP:\n case Protobuf.Portnums.PortNum.DETECTION_SENSOR_APP:\n case Protobuf.Portnums.PortNum.REPLY_APP:\n // Encode string as ASCII/UTF-8 bytes\n if (typeof message !== 'string') {\n throw new Error(`${portNum} requires a string message`);\n }\n return new TextEncoder().encode(message);\n\n case Protobuf.Portnums.PortNum.REMOTE_HARDWARE_APP:\n return Protobuf.RemoteHardware.HardwareMessage.toBinary(message as Protobuf.RemoteHardware.HardwareMessage);\n\n case Protobuf.Portnums.PortNum.TRACEROUTE_APP:\n return Protobuf.Mesh.RouteDiscovery.toBinary(message as Protobuf.Mesh.RouteDiscovery);\n\n case Protobuf.Portnums.PortNum.PAXCOUNTER_APP:\n return Protobuf.Paxcount.Paxcount.toBinary(message as Protobuf.Paxcount.Paxcount);\n\n case Protobuf.Portnums.PortNum.MAP_REPORT_APP:\n return Protobuf.Mqtt.MapReport.toBinary(message as Protobuf.Mqtt.MapReport);\n\n default:\n throw new Error(`Encoding not supported for portNum: ${portNum}`);\n }\n } catch (error) {\n throw new MeshtasticEncodeError(\n `Failed to encode message for portNum ${portNum}: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Create a Data protobuf from encoded payload\n * @param portNum - Port number (message type)\n * @param payload - Encoded message payload\n * @param options - Additional options (wantResponse, requestId, emoji)\n * @returns Data protobuf\n */\nexport function createDataPayload(\n portNum: Protobuf.Portnums.PortNum | number,\n payload: Uint8Array,\n options?: {\n wantResponse?: boolean;\n requestId?: number;\n emoji?: number;\n }\n): Protobuf.Mesh.Data {\n return {\n portnum: portNum as Protobuf.Portnums.PortNum,\n payload,\n wantResponse: options?.wantResponse ?? false,\n requestId: options?.requestId ?? 0,\n emoji: options?.emoji ?? 0,\n };\n}\n\n/**\n * Create a ServiceEnvelope with a Data payload\n * @param data - Data protobuf\n * @param from - Source node number\n * @param to - Destination node number (0xFFFFFFFF for broadcast)\n * @param channel - Channel index\n * @param packetId - Packet ID (if undefined, needs to be set later)\n * @param options - Additional options\n * @returns ServiceEnvelope\n */\nexport function createServiceEnvelope(\n data: Protobuf.Mesh.Data,\n from: number,\n to: number = 0xFFFFFFFF,\n channel: number = 0,\n packetId?: number,\n options?: {\n wantAck?: boolean;\n hopLimit?: number;\n priority?: Protobuf.Mesh.MeshPacket_Priority;\n channelId?: string;\n gatewayId?: string;\n }\n): Protobuf.Mqtt.ServiceEnvelope {\n return {\n packet: {\n from,\n to,\n channel,\n id: packetId ?? 0,\n rxTime: 0,\n rxSnr: 0,\n rxRssi: 0,\n hopLimit: options?.hopLimit ?? 3,\n wantAck: options?.wantAck ?? false,\n priority: options?.priority ?? Protobuf.Mesh.MeshPacket_Priority.UNSET,\n hopStart: options?.hopLimit ?? 3,\n payloadVariant: {\n case: 'decoded',\n value: data,\n },\n },\n channelId: options?.channelId ?? '',\n gatewayId: options?.gatewayId ?? '',\n };\n}\n\n/**\n * Encode a complete ServiceEnvelope to binary\n * @param envelope - ServiceEnvelope to encode\n * @returns Binary encoded ServiceEnvelope\n * @throws {ProtobufError} If encoding fails\n */\nexport function encodeServiceEnvelope(envelope: Protobuf.Mqtt.ServiceEnvelope): Buffer {\n try {\n const binary = Protobuf.Mqtt.ServiceEnvelope.toBinary(envelope);\n return ensureBuffer(binary);\n } catch (error) {\n throw new ProtobufError(\n `Failed to encode ServiceEnvelope: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Encode a Data protobuf to binary\n * @param data - Data protobuf to encode\n * @returns Binary encoded Data\n * @throws {ProtobufError} If encoding fails\n */\nexport function encodeData(data: Protobuf.Mesh.Data): Buffer {\n try {\n const binary = Protobuf.Mesh.Data.toBinary(data);\n return ensureBuffer(binary);\n } catch (error) {\n throw new ProtobufError(\n `Failed to encode Data: ${error instanceof Error ? error.message : String(error)}`,\n error instanceof Error ? error : undefined\n );\n }\n}\n\n/**\n * Parse port number from string name or number\n * @param portNum - Port number as string name or number\n * @returns Port number as enum value\n * @throws {MeshtasticEncodeError} If port number is invalid\n */\nexport function parsePortNum(portNum: string | number): Protobuf.Portnums.PortNum {\n if (typeof portNum === 'number') {\n // Validate that the number is a valid PortNum\n if (portNum in Protobuf.Portnums.PortNum) {\n return portNum as Protobuf.Portnums.PortNum;\n }\n throw new MeshtasticEncodeError(`Invalid port number: ${portNum}`);\n }\n\n // Parse string name to enum value\n const enumValue = (Protobuf.Portnums.PortNum as any)[portNum];\n if (enumValue === undefined) {\n throw new MeshtasticEncodeError(`Unknown port number name: ${portNum}`);\n }\n\n return enumValue;\n}\n"],"names":["Protobuf","MeshtasticEncodeError","ensureBuffer","ProtobufError"],"mappings":"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"}