Repository: gchq/CyberChef
File: src/core/lib/Modhex.mjs (JavaScript)

Code before:
 * toModhex([10,20,30], ":");
 */
export function toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
    if (!data) return "";
    if (data instanceof ArrayBuffer) data = new Uint8Array(data);

    const hexconv = toHex(data, "", padding, "", 0);
    let modhexconv = "";
    let output = "";

    for (const letter of hexconv.split("")) {
        modhexconv += HEX_TO_MODHEX_CONVERSION_MAP[letter];
    }

    const groupedModhex = modhexconv.match(/.{1,2}/g);

    for (let i = 0; i < groupedModhex.length; i++) {
        const group = groupedModhex[i];
        output += group + delim;

        if (extraDelim) {
            output += extraDelim;
        }
        // Add LF after each lineSize amount of bytes but not at the end
        if ((i !== groupedModhex.length - 1) && ((i + 1) % lineSize === 0)) {
            output += "\n";
        }
    }

    // Remove the extraDelim at the end (if there is one)
    // and remove the delim at the end, but if it's prepended there's nothing to remove
    const rTruncLen = extraDelim.length + delim.length;
    if (rTruncLen) {
        // If rTruncLen === 0 then output.slice(0,0) will be returned, which is nothing
        return output.slice(0, -rTruncLen);
    } else {
        return output;
    }
}

/**
 * Convert a hex string into a byte array.
 *
 * @param {string} data
 * @param {string} [delim]
 * @param {number} [byteLen=2]
 * @returns {byteArray}
 *
 * @example
 * // returns [10,20,30]
 * fromModhex("cl bf bu");

Review comment (suggestion):
Generally we only use lib/* when multiple operations share the same constants or functions. I'd recommend just moving these functions into the operations files and only keeping the constants used by both operations.

Code after:
 */
const HEX_ALPHABET = "0123456789abcdef";


/**
 * Hex alphabet map to substitute Modhex.
 */
const HEX_ALPHABET_MAP = HEX_ALPHABET.split("");


/**
 * Convert a byte array into a modhex string.
 *
 * @param {byteArray|Uint8Array|ArrayBuffer} data
 * @param {string} [delim=" "]
 * @param {number} [padding=2]
 * @returns {string}
 *
 * @example
 * // returns "cl bf bu"
 * toModhex([10,20,30]);
 *
 * // returns "cl:bf:bu"
 * toModhex([10,20,30], ":");
 */
export function toModhex(data, delim=" ", padding=2, extraDelim="", lineSize=0) {
    if (!data) return "";
    if (data instanceof ArrayBuffer) data = new Uint8Array(data);

    const regularHexString = toHex(data, "", padding, "", 0);

    let modhexString = "";
    for (const letter of regularHexString.split("")) {
        modhexString += MODHEX_ALPHABET_MAP[HEX_ALPHABET_MAP.indexOf(letter)];
    }

    let output = "";
    const groupingRegexp = new RegExp(`.{1,${padding}}`, "g");
    const groupedModhex = modhexString.match(groupingRegexp);

    for (let i = 0; i < groupedModhex.length; i++) {
        const group = groupedModhex[i];
        output += group + delim;

        if (extraDelim) {
            output += extraDelim;
        }
        // Add LF after each lineSize amount of bytes but not at the end
        if ((i !== groupedModhex.length - 1) && ((i + 1) % lineSize === 0)) {
            output += "\n";
        }