diff --git a/cli/package.json b/cli/package.json index 55284a6c89..79cc246cf3 100644 --- a/cli/package.json +++ b/cli/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "axios": "^0.27.2", + "cbor": "^9.0.1", "chalk": "~4.1.2", "cli-table3": "^0.6.3", "commander": "^9.3.0", diff --git a/cli/src/index.ts b/cli/src/index.ts index 7b93597a94..f63f5a1e17 100755 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -142,7 +142,11 @@ export class Commander { .option('-V, --mqtt-version <5/3.1.1/3.1>', 'the MQTT version', parseMQTTVersion, 5) .option('-h, --hostname ', 'the broker host', 'localhost') .option('-p, --port ', 'the broker port', parseNumber) - .option('-f, --format ', 'the format type of the input message, support base64, json, hex', parseFormat) + .option( + '-f, --format ', + 'the format type of the input message, support base64, json, hex and cbor', + parseFormat, + ) .option('-i, --client-id ', 'the client id', getClientId()) .option('--no-clean', 'set the clean session flag to false', true) .option('-k, --keepalive ', 'send a ping every SEC seconds', parseNumber, 30) @@ -230,7 +234,7 @@ export class Commander { 'the user properties of MQTT 5.0 (e.g. -up "name: mqttx cli")', parseUserProperties, ) - .option('-f, --format ', 'format the message body, support base64, json, hex', parseFormat) + .option('-f, --format ', 'format the message body, support base64, json, hex and cbor', parseFormat) .option('-v, --verbose', 'print the topic before the message') .option( '--output-mode ', diff --git a/cli/src/lib/pub.ts b/cli/src/lib/pub.ts index 7a3f9cd433..a6be8ad322 100644 --- a/cli/src/lib/pub.ts +++ b/cli/src/lib/pub.ts @@ -15,9 +15,9 @@ import * as Debug from 'debug' const processPublishMessage = ( message: string | Buffer, - protobufPath: string | undefined, - protobufMessageName: string | undefined, - format: FormatType | undefined, + protobufPath?: string, + protobufMessageName?: string, + format?: FormatType, ): Buffer | string => { /* * Pipeline for processing outgoing messages in two potential stages: diff --git a/cli/src/lib/sub.ts b/cli/src/lib/sub.ts index cc2f081ff1..cfb6977f60 100644 --- a/cli/src/lib/sub.ts +++ b/cli/src/lib/sub.ts @@ -9,9 +9,9 @@ import * as Debug from 'debug' const processReceivedMessage = ( payload: Buffer, - protobufPath: string | undefined, - protobufMessageName: string | undefined, - format: FormatType | undefined, + protobufPath?: string, + protobufMessageName?: string, + format?: FormatType, ): string => { let message: string | Buffer = payload /* diff --git a/cli/src/types/global.d.ts b/cli/src/types/global.d.ts index d0336497b0..5650e703c4 100644 --- a/cli/src/types/global.d.ts +++ b/cli/src/types/global.d.ts @@ -9,7 +9,7 @@ declare global { type QoS = 0 | 1 | 2 - type FormatType = 'base64' | 'json' | 'hex' + type FormatType = 'base64' | 'json' | 'hex' | 'cbor' type OutputMode = 'clean' | 'default' diff --git a/cli/src/utils/convertPayload.ts b/cli/src/utils/convertPayload.ts index 9dcf6d52a4..de0e49712d 100644 --- a/cli/src/utils/convertPayload.ts +++ b/cli/src/utils/convertPayload.ts @@ -1,30 +1,68 @@ import chalk from 'chalk' import { jsonParse, jsonStringify } from './jsonUtils' +import cbor from 'cbor' +import { basicLog } from './signale' -const convertJSON = (value: Buffer | string, action: 'encode' | 'decode') => { +type Action = 'encode' | 'decode' + +const handleError = (err: unknown, value: Buffer | string, action: Action) => { + basicLog.error(err as Error) + return action === 'decode' ? chalk.red(value.toString()) : process.exit(1) +} + +/** + * Converts a JSON payload to a Buffer or string based on the specified action. + * @param value - The JSON payload to convert. + * @param action - The action to perform on the payload ('decode' or 'encode'). + * @returns The converted payload. + */ +const convertJSON = (value: Buffer | string, action: Action) => { + try { + return action === 'decode' + ? jsonStringify(jsonParse(value.toString()), null, 2) + : Buffer.from(jsonStringify(jsonParse(value.toString()))) + } catch (err) { + return handleError(err, value, action) + } +} + +/** + * Converts a CBOR payload to JSON or vice versa. + * @param value - The CBOR payload to convert. + * @param action - The action to perform: 'decode' to convert CBOR to JSON, 'encode' to convert JSON to CBOR. + * @returns The converted payload. + */ +const convertCBOR = (value: Buffer | string, action: Action) => { try { - if (action === 'decode') { - return jsonStringify(jsonParse(value.toString()), null, 2) - } else { - return Buffer.from(jsonStringify(jsonParse(value.toString()))) - } + return action === 'decode' + ? jsonStringify(cbor.decodeFirstSync(value), null, 2) + : cbor.encodeOne(JSON.parse(value.toString())) } catch (err) { - return chalk.red(err) + return handleError(err, value, action) } } -const convertPayload = (payload: Buffer | string, format?: FormatType, action: 'encode' | 'decode' = 'decode') => { +/** + * Converts the payload based on the specified format and action. + * @param payload - The payload to be converted. + * @param format - The format in which the payload should be converted. (Optional) + * @param action - The action to be performed on the payload. (Default: 'decode') + * @returns The converted payload. + */ +const convertPayload = (payload: Buffer | string, format?: FormatType, action: Action = 'decode') => { const actions = { encode: { base64: () => Buffer.from(payload.toString(), 'base64'), json: () => convertJSON(payload, 'encode'), hex: () => Buffer.from(payload.toString().replace(/\s+/g, ''), 'hex'), + cbor: () => convertCBOR(payload, 'encode'), default: () => Buffer.from(payload.toString(), 'utf-8'), }, decode: { base64: () => payload.toString('base64'), json: () => convertJSON(payload, 'decode'), hex: () => payload.toString('hex').replace(/(.{4})/g, '$1 '), + cbor: () => convertCBOR(payload, 'decode'), default: () => payload.toString('utf-8'), }, } diff --git a/cli/src/utils/parse.ts b/cli/src/utils/parse.ts index 4c2f3e08e6..76c30d4d90 100644 --- a/cli/src/utils/parse.ts +++ b/cli/src/utils/parse.ts @@ -98,7 +98,7 @@ const parsePubTopic = (value: string) => { } const parseFormat = (value: string) => { - if (!['base64', 'json', 'hex'].includes(value)) { + if (!['base64', 'json', 'hex', 'cbor'].includes(value)) { signale.error('Not a valid format type.') process.exit(1) } diff --git a/cli/yarn.lock b/cli/yarn.lock index 73fb603961..bd8c19b80b 100644 --- a/cli/yarn.lock +++ b/cli/yarn.lock @@ -222,6 +222,13 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +cbor@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.1.tgz#b16e393d4948d44758cd54ac6151379d443b37ae" + integrity sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ== + dependencies: + nofilter "^3.1.0" + chalk@^2.3.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" @@ -579,6 +586,11 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +nofilter@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" + integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g== + number-allocator@^1.0.9: version "1.0.10" resolved "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.10.tgz"