mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-07 14:57:12 -04:00
feat(new tool): Hex Converter
Convert hex buffer to (un)signed integer/float Fix #1447
This commit is contained in:
parent
08d977b8cd
commit
71faa2bb0c
11 changed files with 7085 additions and 8471 deletions
11
components.d.ts
vendored
11
components.d.ts
vendored
|
@ -83,6 +83,7 @@ declare module '@vue/runtime-core' {
|
|||
GitMemo: typeof import('./src/tools/git-memo/git-memo.vue')['default']
|
||||
'GitMemo.content': typeof import('./src/tools/git-memo/git-memo.content.md')['default']
|
||||
HashText: typeof import('./src/tools/hash-text/hash-text.vue')['default']
|
||||
HexConverter: typeof import('./src/tools/hex-converter/hex-converter.vue')['default']
|
||||
HmacGenerator: typeof import('./src/tools/hmac-generator/hmac-generator.vue')['default']
|
||||
'Home.page': typeof import('./src/pages/Home.page.vue')['default']
|
||||
HtmlEntities: typeof import('./src/tools/html-entities/html-entities.vue')['default']
|
||||
|
@ -131,18 +132,24 @@ declare module '@vue/runtime-core' {
|
|||
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
|
||||
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCode: typeof import('naive-ui')['NCode']
|
||||
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
|
||||
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
|
||||
NDivider: typeof import('naive-ui')['NDivider']
|
||||
NEllipsis: typeof import('naive-ui')['NEllipsis']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
NH1: typeof import('naive-ui')['NH1']
|
||||
NH3: typeof import('naive-ui')['NH3']
|
||||
NIcon: typeof import('naive-ui')['NIcon']
|
||||
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||
NLayout: typeof import('naive-ui')['NLayout']
|
||||
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
|
||||
NMenu: typeof import('naive-ui')['NMenu']
|
||||
NP: typeof import('naive-ui')['NP']
|
||||
NRadio: typeof import('naive-ui')['NRadio']
|
||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||
NScrollbar: typeof import('naive-ui')['NScrollbar']
|
||||
NSpace: typeof import('naive-ui')['NSpace']
|
||||
NTable: typeof import('naive-ui')['NTable']
|
||||
NSwitch: typeof import('naive-ui')['NSwitch']
|
||||
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
|
||||
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
|
||||
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
|
||||
|
|
|
@ -53,6 +53,7 @@
|
|||
"@vueuse/head": "^1.0.0",
|
||||
"@vueuse/router": "^10.0.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"byte-data": "^19.0.1",
|
||||
"change-case": "^4.1.2",
|
||||
"colord": "^2.9.3",
|
||||
"composerize-ts": "^0.6.2",
|
||||
|
@ -67,6 +68,7 @@
|
|||
"figlet": "^1.7.0",
|
||||
"figue": "^1.2.0",
|
||||
"fuse.js": "^6.6.2",
|
||||
"hex-array": "^1.0.0",
|
||||
"highlight.js": "^11.7.0",
|
||||
"iarna-toml-esm": "^3.0.5",
|
||||
"ibantools": "^4.3.3",
|
||||
|
@ -115,6 +117,7 @@
|
|||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/hex-array": "^1.0.2",
|
||||
"@types/jsdom": "^21.0.0",
|
||||
"@types/lodash": "^4.14.192",
|
||||
"@types/mime-types": "^2.1.1",
|
||||
|
|
14152
pnpm-lock.yaml
generated
14152
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -128,7 +128,7 @@ function activateOption(option: PaletteOption) {
|
|||
<c-input-text ref="inputRef" v-model:value="searchPrompt" raw-text placeholder="Type to search a tool or a command..." autofocus clearable />
|
||||
|
||||
<div v-for="(options, category) in filteredSearchResult" :key="category">
|
||||
<div ml-3 mt-3 text-sm font-bold text-primary op-60>
|
||||
<div ml-3 mt-3 text-sm text-primary font-bold op-60>
|
||||
{{ category }}
|
||||
</div>
|
||||
<command-palette-option v-for="option in options" :key="option.name" :option="option" :selected="selectedOptionIndex === getOptionIndex(option)" @activated="activateOption" />
|
||||
|
|
250
src/tools/hex-converter/hex-converter.service.test.ts
Normal file
250
src/tools/hex-converter/hex-converter.service.test.ts
Normal file
|
@ -0,0 +1,250 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { cleanHex, decodeNumber, decodeStruct, encodeStruct, getCoderFromTypeName, parseNumber } from './hex-converter.service';
|
||||
|
||||
describe('cleanHex', () => {
|
||||
it('should remove 0x and \\x prefixes from a hex string', () => {
|
||||
expect(cleanHex('0x1234')).toBe('1234');
|
||||
expect(cleanHex('\\x1234')).toBe('1234');
|
||||
expect(cleanHex('1234')).toBe('1234');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decodeNumber', () => {
|
||||
it('should convert a number to binary', () => {
|
||||
expect(decodeNumber(10, 8, 'bin')).toBe('00001010');
|
||||
});
|
||||
|
||||
it('should convert a number to hex', () => {
|
||||
expect(decodeNumber(10, 8, 'hex')).toBe('0a');
|
||||
});
|
||||
|
||||
it('should convert a number to character', () => {
|
||||
expect(decodeNumber(65, 8, 'char')).toBe('A');
|
||||
});
|
||||
|
||||
it('should return the number itself for decimal conversion', () => {
|
||||
expect(decodeNumber(10, 8, 'dec')).toBe(10);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseNumber', () => {
|
||||
it('should parse hexadecimal input correctly', () => {
|
||||
expect(parseNumber('0x1F')).toBe(31);
|
||||
});
|
||||
|
||||
it('should parse binary input correctly', () => {
|
||||
expect(parseNumber('0b1101')).toBe(13);
|
||||
});
|
||||
|
||||
it('should parse string input correctly into code points', () => {
|
||||
expect(parseNumber('abc')).toEqual([97, 98, 99]);
|
||||
});
|
||||
|
||||
it('should return number when input is already a number', () => {
|
||||
expect(parseNumber(42)).toBe(42);
|
||||
});
|
||||
|
||||
it('should return 0 for falsy input', () => {
|
||||
expect(parseNumber('')).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCoderFromTypeName', () => {
|
||||
it('should return correct coder options for a basic integer type', () => {
|
||||
const result = getCoderFromTypeName('uint16');
|
||||
expect(result.type.bits).toBe(16);
|
||||
expect(result.type.signed).toBe(false);
|
||||
expect(result.size).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle big-endian types correctly', () => {
|
||||
const result = getCoderFromTypeName('int32be');
|
||||
expect(result.type.bits).toBe(32);
|
||||
expect(result.type.be).toBe(true);
|
||||
});
|
||||
|
||||
it('should return correct coder options for a float type', () => {
|
||||
const result = getCoderFromTypeName('float');
|
||||
expect(result.type.bits).toBe(32);
|
||||
expect(result.type.fp).toBe(true);
|
||||
});
|
||||
|
||||
it('should return correct coder options for a character type', () => {
|
||||
const result = getCoderFromTypeName('char[4]');
|
||||
expect(result.type.bits).toBe(8);
|
||||
expect(result.size).toBe(4);
|
||||
expect(result.formatter(65, 8)).toBe('A');
|
||||
});
|
||||
});
|
||||
|
||||
describe('decodeStruct', () => {
|
||||
it('should decode a flat struct correctly from a hex array', () => {
|
||||
const struct = {
|
||||
field1: 'uint16',
|
||||
field2: 'char[3]',
|
||||
};
|
||||
const hexArray = new Uint8Array([0x01, 0x0, 0x61, 0x62, 0x63]);
|
||||
|
||||
const result = decodeStruct({ struct, hexArray });
|
||||
expect(result.field1).toBe(1);
|
||||
expect(result.field2).toBe('abc');
|
||||
});
|
||||
|
||||
it('should decode a nested struct correctly', () => {
|
||||
const struct = {
|
||||
type: 'int8',
|
||||
header: {
|
||||
version: 'uint8',
|
||||
type: 'uint16be',
|
||||
},
|
||||
payload: {
|
||||
value: 'floatbe',
|
||||
},
|
||||
};
|
||||
const hexArray = new Uint8Array([0xFF, 0x01, 0x00, 0x02, 0x3F, 0x80, 0x0, 0x0]); // float 1.0
|
||||
|
||||
const result = decodeStruct({ struct, hexArray });
|
||||
expect(result).toEqual({
|
||||
type: -1,
|
||||
header: { version: 1, type: 2 },
|
||||
payload: { value: 1.0 },
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw on bad buffer length', () => {
|
||||
const struct = {
|
||||
type: 'int8',
|
||||
header: {
|
||||
version: 'uint8',
|
||||
type: 'uint16be',
|
||||
},
|
||||
payload: {
|
||||
value: 'floatbe',
|
||||
},
|
||||
};
|
||||
const hexArray = new Uint8Array([0xFF, 0x01, 0x00, 0x02, 0x3F, 0x80]); // missing last two bytes
|
||||
|
||||
expect(() => decodeStruct({ struct, hexArray })).toThrowError(
|
||||
'Bad buffer length reading value(floatbe) at offset 4',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error when decoding an unsupported array type', () => {
|
||||
const struct = {
|
||||
list: ['uint8'],
|
||||
};
|
||||
const hexArray = new Uint8Array([0x01, 0x02, 0x03]);
|
||||
|
||||
expect(() => decodeStruct({ struct, hexArray })).toThrowError(
|
||||
'Cannot decode a struct with array',
|
||||
);
|
||||
});
|
||||
it('should throw an error when decoding an unsized array type', () => {
|
||||
const struct = {
|
||||
list: 'uint8[]',
|
||||
};
|
||||
const hexArray = new Uint8Array([0x01, 0x02, 0x03]);
|
||||
|
||||
expect(() => decodeStruct({ struct, hexArray })).toThrowError(
|
||||
'Unsupported unsized array: uint8[]',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('encodeStruct', () => {
|
||||
it('should encode a flat struct correctly', () => {
|
||||
const struct = {
|
||||
field1: 'uint8',
|
||||
field2: 'char[3]',
|
||||
};
|
||||
const jsonObject = {
|
||||
field1: 1,
|
||||
field2: 'abc',
|
||||
};
|
||||
|
||||
const result = encodeStruct({ struct, jsonObject });
|
||||
expect(result).toEqual(new Uint8Array([0x01, 0x61, 0x62, 0x63]));
|
||||
});
|
||||
|
||||
it('should encode a nested struct correctly', () => {
|
||||
const struct = {
|
||||
type: 'int8',
|
||||
header: {
|
||||
version: 'uint8',
|
||||
type: 'uint16be',
|
||||
},
|
||||
payload: {
|
||||
value: 'floatbe',
|
||||
},
|
||||
};
|
||||
const jsonObject = {
|
||||
type: -1,
|
||||
header: { version: 1, type: 2 },
|
||||
payload: { value: 1.0 },
|
||||
};
|
||||
|
||||
const result = encodeStruct({ struct, jsonObject });
|
||||
expect(result).toEqual(new Uint8Array([0xFF, 0x01, 0x00, 0x02, 0x3F, 0x80, 0x0, 0x0]));
|
||||
});
|
||||
|
||||
it('should throw an error if array size is incorrect', () => {
|
||||
const struct = {
|
||||
field1: 'uint8',
|
||||
field2: 'char[3]',
|
||||
};
|
||||
const jsonObject = {
|
||||
field1: 1,
|
||||
field2: 'abcd', // Too long
|
||||
};
|
||||
|
||||
expect(() => encodeStruct({ struct, jsonObject })).toThrowError(
|
||||
'Unexpected array size \'field2\'=\'97,98,99,100\' expected 3 elements',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if value type is incorrect', () => {
|
||||
const struct = {
|
||||
field1: 'uint8',
|
||||
field2: 'char[3]',
|
||||
};
|
||||
const jsonObject = {
|
||||
field1: 1,
|
||||
field2: 123, // Invalid type
|
||||
};
|
||||
|
||||
expect(() => encodeStruct({ struct, jsonObject })).toThrowError(
|
||||
'Unexpected non array \'field2\'=\'123\'',
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if value type is incorrect', () => {
|
||||
const struct = {
|
||||
field1: 'uint8',
|
||||
field2: 'int16',
|
||||
};
|
||||
const jsonObject = {
|
||||
field1: 1,
|
||||
field2: '123', // Invalid type
|
||||
};
|
||||
|
||||
expect(() => encodeStruct({ struct, jsonObject })).toThrowError(
|
||||
'Unexpected array size \'field2\'=\'49,50,51\' expected 1 elements',
|
||||
);
|
||||
});
|
||||
|
||||
it('should encode multiple integer types correctly', () => {
|
||||
const struct = {
|
||||
a: 'uint8',
|
||||
b: 'uint16',
|
||||
c: 'uint32',
|
||||
};
|
||||
const jsonObject = {
|
||||
a: 1,
|
||||
b: 513,
|
||||
c: 67305985,
|
||||
};
|
||||
|
||||
const result = encodeStruct({ struct, jsonObject });
|
||||
expect(result).toEqual(new Uint8Array([0x01, 0x01, 0x02, 0x01, 0x02, 0x03, 0x04]));
|
||||
});
|
||||
});
|
193
src/tools/hex-converter/hex-converter.service.ts
Normal file
193
src/tools/hex-converter/hex-converter.service.ts
Normal file
|
@ -0,0 +1,193 @@
|
|||
import { pack, unpack } from 'byte-data';
|
||||
|
||||
export type Conversion = 'dec' | 'bin' | 'hex' | 'char';
|
||||
|
||||
export function cleanHex(hex: string): string {
|
||||
return hex.replace(/\\x|0x/g, '');
|
||||
}
|
||||
|
||||
export function decodeNumber(n: number, bits: number, conv: Conversion) {
|
||||
if (conv === 'bin') {
|
||||
return n.toString(2).padStart(bits, '0');
|
||||
}
|
||||
if (conv === 'hex') {
|
||||
return n.toString(16).padStart(bits / 4, '0');
|
||||
}
|
||||
if (conv === 'char') {
|
||||
return String.fromCodePoint(n);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
export function parseNumber(input: string | number): number | number[] {
|
||||
if (!input) {
|
||||
return 0;
|
||||
}
|
||||
if (typeof input === 'number') {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (/^0x[0-9a-fA-F]+$/.test(input)) {
|
||||
return Number.parseInt(input.substring(2), 16); // Parse as hexadecimal
|
||||
}
|
||||
else if (/^0b[01]+$/.test(input)) {
|
||||
return Number.parseInt(input.substring(2), 2); // Parse as binary
|
||||
}
|
||||
|
||||
return [...input].map(c => c.codePointAt(0) || 0);
|
||||
}
|
||||
|
||||
function mergeModelAndObject(model: Record<string, any>, object: Record<string, any>): Record<string, any> {
|
||||
const merged: Record<string, any> = {};
|
||||
|
||||
for (const key in model) {
|
||||
if (Object.prototype.hasOwnProperty.call(model, key)) {
|
||||
if (Array.isArray(object[key])) {
|
||||
merged[key] = [model[key], object[key].map(parseNumber)];
|
||||
}
|
||||
else if (typeof object[key] === 'object' && !Array.isArray(object[key])) {
|
||||
merged[key] = mergeModelAndObject(model[key], object[key]);
|
||||
}
|
||||
else {
|
||||
merged[key] = [model[key], parseNumber(object[key])];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
interface CoderOption {
|
||||
type: {
|
||||
bits: number
|
||||
fp?: boolean
|
||||
be?: boolean
|
||||
signed?: boolean
|
||||
}
|
||||
size: number
|
||||
formatter: (n: number, bits: number) => string | number
|
||||
join?: boolean
|
||||
};
|
||||
|
||||
export function getCoderFromTypeName(typeName: string): CoderOption {
|
||||
if (typeName.includes('[]')) {
|
||||
throw new Error(`Unsupported unsized array: ${typeName}`);
|
||||
}
|
||||
const [, prefix, baseTypeName, bigEndian, arraySize] = /^((?:0x|0b)?)(u?int\d+|w?char|half|float|double)(be)?(?:\[(\d+)\])?$/.exec(typeName) || [];
|
||||
let conv = 'dec';
|
||||
if (prefix === '0x') {
|
||||
conv = 'hex';
|
||||
}
|
||||
if (prefix === '0b') {
|
||||
conv = 'bin';
|
||||
}
|
||||
|
||||
const arraySizeNumber = Number.isNaN(Number(arraySize)) ? 1 : Number(arraySize);
|
||||
if (baseTypeName === 'char' || baseTypeName === 'wchar') {
|
||||
return {
|
||||
type: {
|
||||
bits: baseTypeName === 'char' ? 8 : 16,
|
||||
be: !!bigEndian,
|
||||
},
|
||||
size: arraySizeNumber,
|
||||
formatter: n => String.fromCodePoint(n),
|
||||
join: true,
|
||||
};
|
||||
}
|
||||
if (baseTypeName === 'float' || baseTypeName === 'double' || baseTypeName === 'half') {
|
||||
return {
|
||||
type: {
|
||||
bits: baseTypeName === 'float' ? 32 : (baseTypeName === 'double' ? 64 : 16),
|
||||
be: !!bigEndian,
|
||||
fp: true,
|
||||
},
|
||||
size: arraySizeNumber,
|
||||
formatter: n => n,
|
||||
};
|
||||
}
|
||||
|
||||
const [, unsigned, bits] = /^(u?)int(\d+)$/.exec(baseTypeName || '') || [];
|
||||
return {
|
||||
type: {
|
||||
bits: Number(bits),
|
||||
be: !!bigEndian,
|
||||
signed: !unsigned,
|
||||
},
|
||||
size: arraySizeNumber,
|
||||
formatter: (n, bits) => decodeNumber(n, bits, conv as Conversion),
|
||||
};
|
||||
}
|
||||
|
||||
export function decodeStruct({ struct, hexArray }: { struct: object; hexArray: Uint8Array }) {
|
||||
let offset = 0;
|
||||
const readMember = (obj: any) => {
|
||||
const result: Record<string, any> = {};
|
||||
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
if (Array.isArray(obj[key])) {
|
||||
throw new TypeError(`Cannot decode a struct with array (key=${key}). Must be expressed as string with fixed length`);
|
||||
}
|
||||
else if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||||
result[key] = readMember(obj[key]);
|
||||
}
|
||||
else {
|
||||
const coderOption = getCoderFromTypeName(obj[key]);
|
||||
const arr = [];
|
||||
for (let i = 0; i < coderOption.size; i++) {
|
||||
const dataSize = Math.ceil(coderOption.type.bits / 8);
|
||||
if (offset + dataSize > hexArray.length) {
|
||||
throw new Error(`Bad buffer length reading ${key}(${obj[key]}) at offset ${offset}`);
|
||||
}
|
||||
arr.push(coderOption.formatter(unpack(hexArray, coderOption.type, offset), coderOption.type.bits));
|
||||
offset += dataSize;
|
||||
}
|
||||
if (coderOption.join) {
|
||||
result[key] = arr.join('');
|
||||
}
|
||||
else {
|
||||
result[key] = coderOption.size > 1 ? arr : arr[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
return readMember(struct);
|
||||
}
|
||||
|
||||
export function encodeStruct({ struct, jsonObject }: { struct: object; jsonObject: object }): Uint8Array {
|
||||
const mergedObject = mergeModelAndObject(struct, jsonObject);
|
||||
|
||||
let buffer: Array<number> = [];
|
||||
const writeMember = (obj: any) => {
|
||||
for (const key in obj) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||||
writeMember(obj[key]);
|
||||
}
|
||||
else if (Array.isArray(obj[key])) {
|
||||
const [typeName, value] = obj[key];
|
||||
const coderOption = getCoderFromTypeName(typeName);
|
||||
if (coderOption.size > 1 && !Array.isArray(value)) {
|
||||
throw new TypeError(`Unexpected non array '${key}'='${value}'`);
|
||||
}
|
||||
if (Array.isArray(value) && value.length !== coderOption.size) {
|
||||
throw new TypeError(`Unexpected array size '${key}'='${value}' expected ${coderOption.size} elements`);
|
||||
}
|
||||
const valueArr = !Array.isArray(value) ? [value] : value;
|
||||
for (let i = 0; i < coderOption.size; i++) {
|
||||
buffer = [...buffer, ...pack(valueArr[i], coderOption.type)];
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new TypeError(`Unexpected '${key}'='${obj[key]}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
writeMember(mergedObject);
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
303
src/tools/hex-converter/hex-converter.vue
Normal file
303
src/tools/hex-converter/hex-converter.vue
Normal file
|
@ -0,0 +1,303 @@
|
|||
<script setup lang="ts">
|
||||
import hexArray from 'hex-array';
|
||||
import { packArray, packString, unpackArray, unpackString } from 'byte-data';
|
||||
import JSON5 from 'json5';
|
||||
import { type Conversion, cleanHex, decodeNumber, decodeStruct, encodeStruct } from './hex-converter.service';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
|
||||
const mode = ref<'simple' | 'struct'>('simple');
|
||||
|
||||
const bits = ref(32);
|
||||
const floatingPoint = ref(false);
|
||||
const signed = ref(false);
|
||||
const bigEndian = ref(false);
|
||||
const decodeAs = ref<'dec' | 'bin' | 'hexa' | 'char' | 'utf8'>('dec');
|
||||
|
||||
const uppercase = ref(false);
|
||||
const grouping = ref(1);
|
||||
const rowlength = ref(0);
|
||||
|
||||
const hexInput = ref('');
|
||||
const decodedOutput = computed(() => {
|
||||
try {
|
||||
const buffer = hexArray.fromString(cleanHex(hexInput.value));
|
||||
if (decodeAs.value === 'utf8') {
|
||||
return unpackString(buffer);
|
||||
}
|
||||
else {
|
||||
return unpackArray(buffer, {
|
||||
bits: bits.value,
|
||||
fp: floatingPoint.value,
|
||||
signed: signed.value,
|
||||
be: bigEndian.value,
|
||||
}, 0, buffer.length, true).map(n => decodeNumber(n, bits.value, decodeAs.value as Conversion)).join(' ');
|
||||
}
|
||||
}
|
||||
catch (e: any) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
const numberInput = ref('');
|
||||
const encodedOutput = computed(() => {
|
||||
try {
|
||||
const values = numberInput.value.split(/\s+/).map(Number);
|
||||
return hexArray.toString(
|
||||
new Uint8Array(
|
||||
packArray(values, {
|
||||
bits: bits.value,
|
||||
fp: floatingPoint.value,
|
||||
signed: signed.value,
|
||||
be: bigEndian.value,
|
||||
})),
|
||||
{
|
||||
uppercase: uppercase.value,
|
||||
grouping: grouping.value,
|
||||
rowlength: rowlength.value,
|
||||
},
|
||||
);
|
||||
}
|
||||
catch (e: any) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
|
||||
const stringInput = ref('');
|
||||
const utf8Output = computed(() => {
|
||||
try {
|
||||
return hexArray.toString(
|
||||
new Uint8Array(packString(stringInput.value)),
|
||||
{
|
||||
uppercase: uppercase.value,
|
||||
grouping: grouping.value,
|
||||
rowlength: rowlength.value,
|
||||
},
|
||||
);
|
||||
}
|
||||
catch (e: any) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
|
||||
const structDefinition = ref(`{
|
||||
x: "int32",
|
||||
y: "int32",
|
||||
}`);
|
||||
const structDefinitionValidation = useValidation({
|
||||
source: structDefinition,
|
||||
rules: [
|
||||
{
|
||||
message: 'Struct definition is not a valid JSON',
|
||||
validator: value => JSON5.parse(value.trim()),
|
||||
},
|
||||
],
|
||||
});
|
||||
const hexStructInput = ref('');
|
||||
const decodedStructOutput = computed(() => {
|
||||
try {
|
||||
return JSON.stringify(
|
||||
decodeStruct({
|
||||
struct: JSON5.parse(structDefinition.value),
|
||||
hexArray: hexArray.fromString(cleanHex(hexStructInput.value)),
|
||||
}),
|
||||
null, 2);
|
||||
}
|
||||
catch (e: any) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
|
||||
const jsonStructInput = ref('');
|
||||
const encodedStructOutput = computed(() => {
|
||||
try {
|
||||
return hexArray.toString(
|
||||
encodeStruct({
|
||||
struct: JSON5.parse(structDefinition.value),
|
||||
jsonObject: JSON5.parse(jsonStructInput.value),
|
||||
}), {
|
||||
uppercase: uppercase.value,
|
||||
grouping: grouping.value,
|
||||
rowlength: rowlength.value,
|
||||
});
|
||||
}
|
||||
catch (e: any) {
|
||||
return e.toString();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<n-radio-group v-model:value="mode" name="radiogroup" mb-2 flex justify-center>
|
||||
<n-space>
|
||||
<n-radio
|
||||
value="simple"
|
||||
label="Simple Encoder/Decoder"
|
||||
/>
|
||||
<n-radio
|
||||
value="struct"
|
||||
label="C/C++ typed struct"
|
||||
/>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
|
||||
<div v-if="mode === 'simple'">
|
||||
<c-card title="Hex Options" mb-1>
|
||||
<c-select
|
||||
v-model:value="decodeAs"
|
||||
label="Decode/Encode As:"
|
||||
label-position="left" mb-1
|
||||
:options="[{ value: 'dec', label: 'Decimal' }, { value: 'bin', label: 'Binary' }, { value: 'hex', label: 'Hexadecimal' }, { value: 'char', label: 'Char/ASCII' }, { value: 'utf8', label: 'UTF8 string' }]"
|
||||
/>
|
||||
<n-space v-if="decodeAs !== 'utf8'" align="baseline" justify="center">
|
||||
<n-form-item label="Bits:" label-placement="left">
|
||||
<n-input-number v-model:value="bits" :min="1" style="width: 6em" />
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-checkbox v-model:checked="floatingPoint">
|
||||
Floating Point
|
||||
</n-checkbox>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-checkbox v-model:checked="signed">
|
||||
Signed
|
||||
</n-checkbox>
|
||||
</n-form-item>
|
||||
<n-form-item>
|
||||
<n-checkbox v-model:checked="bigEndian">
|
||||
Big Endian
|
||||
</n-checkbox>
|
||||
</n-form-item>
|
||||
</n-space>
|
||||
</c-card>
|
||||
<c-card title="Hex Data Decoder" mb-3>
|
||||
<c-input-text
|
||||
v-model:value="hexInput"
|
||||
multiline
|
||||
placeholder="Put your Hex data here..."
|
||||
rows="2"
|
||||
label="Hex Data to decode"
|
||||
raw-text
|
||||
mb-5
|
||||
/>
|
||||
|
||||
<n-form-item label="Your decoded values:">
|
||||
<textarea-copyable :value="decodedOutput" />
|
||||
</n-form-item>
|
||||
</c-card>
|
||||
<c-card v-if="decodeAs !== 'utf8'" title="Hex Data Encoder" mt-3>
|
||||
<c-input-text
|
||||
v-model:value="numberInput"
|
||||
multiline
|
||||
placeholder="Put your Numbers array here..."
|
||||
rows="2"
|
||||
label="Numbers array to encode"
|
||||
raw-text
|
||||
mb-5
|
||||
/>
|
||||
|
||||
<n-form-item label="Your encoded numbers array as Hex:">
|
||||
<textarea-copyable :value="encodedOutput" />
|
||||
</n-form-item>
|
||||
</c-card>
|
||||
<c-card v-if="decodeAs === 'utf8'" title="Hex UTF8 String Encoder" mt-3>
|
||||
<c-input-text
|
||||
v-model:value="stringInput"
|
||||
multiline
|
||||
placeholder="Put your text here..."
|
||||
rows="5"
|
||||
label="String to encode"
|
||||
raw-text
|
||||
mb-5
|
||||
/>
|
||||
|
||||
<n-form-item label="Your encoded string as UTF8 Hex:">
|
||||
<textarea-copyable :value="utf8Output" />
|
||||
</n-form-item>
|
||||
</c-card>
|
||||
<c-card title="Hex Encoding Output" mt-1>
|
||||
<n-space align="baseline" justify="center">
|
||||
<n-form-item label="Uppercase" label-placement="left">
|
||||
<n-switch v-model:value="uppercase" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Group by" label-placement="left">
|
||||
<n-input-number v-model:value="grouping" :min="0" style="width: 6em" mr-1 /> digits (0 = no grouping)
|
||||
</n-form-item>
|
||||
<n-form-item label="Split as rows by" label-placement="left">
|
||||
<n-input-number v-model:value="rowlength" :min="0" style="width: 6em" mr-1 /> group of digits (0 = no rows)
|
||||
</n-form-item>
|
||||
</n-space>
|
||||
</c-card>
|
||||
</div>
|
||||
|
||||
<div v-if="mode === 'struct'">
|
||||
<c-card title="Struct Definition">
|
||||
<c-input-text
|
||||
v-model:value="structDefinition"
|
||||
multiline
|
||||
placeholder="Put your Struct defintion here..."
|
||||
rows="5"
|
||||
label="C/C+ like struct definition"
|
||||
raw-text
|
||||
mb-5
|
||||
:validation="structDefinitionValidation"
|
||||
/>
|
||||
|
||||
<details>
|
||||
<summary>Instructions</summary>
|
||||
<n-p>
|
||||
Define you struct definition in JSON format: keys = struct member names ; value = type
|
||||
<br>
|
||||
Types syntax: u?int{size}(be)? | float(be)? | double(be)? | char | wchar(be)? | <type>[{array size}]
|
||||
<br>
|
||||
where "u" means "unsigned" ; "be" means "Big Endian" ; {size} is number of bits ; {array size} fixed size for arrays
|
||||
<br>
|
||||
can prefix integer with 0x or 0b to display as hex and binary
|
||||
</n-p>
|
||||
</details>
|
||||
</c-card>
|
||||
<c-card title="Hex Struct Decoder" m-t-1>
|
||||
<c-input-text
|
||||
v-model:value="hexStructInput"
|
||||
multiline
|
||||
placeholder="Put your Hex data here..."
|
||||
rows="5"
|
||||
label="Hex Data to decode"
|
||||
raw-text
|
||||
mb-5
|
||||
/>
|
||||
|
||||
<n-form-item label="Your decoded values:">
|
||||
<textarea-copyable :value="decodedStructOutput" />
|
||||
</n-form-item>
|
||||
</c-card>
|
||||
<c-card title="Hex Struct Encoder" m-t-1>
|
||||
<c-input-text
|
||||
v-model:value="jsonStructInput"
|
||||
multiline
|
||||
placeholder="Put your Struct to encode here..."
|
||||
rows="5"
|
||||
label="Struct json to encode"
|
||||
raw-text
|
||||
mb-5
|
||||
/>
|
||||
|
||||
<n-form-item label="Your encoded struct as Hex:">
|
||||
<textarea-copyable :value="encodedStructOutput" />
|
||||
</n-form-item>
|
||||
</c-card>
|
||||
<c-card title="Hex Encoding Output" mt-1>
|
||||
<n-space align="baseline" justify="center">
|
||||
<n-form-item label="Uppercase" label-placement="left">
|
||||
<n-switch v-model:value="uppercase" />
|
||||
</n-form-item>
|
||||
<n-form-item label="Group by" label-placement="left">
|
||||
<n-input-number v-model:value="grouping" :min="0" style="width: 6em" mr-1 /> digits (0 = no grouping)
|
||||
</n-form-item>
|
||||
<n-form-item label="Split as rows by" label-placement="left">
|
||||
<n-input-number v-model:value="rowlength" :min="0" style="width: 6em" mr-1 /> group of digits (0 = no rows)
|
||||
</n-form-item>
|
||||
</n-space>
|
||||
</c-card>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
12
src/tools/hex-converter/index.ts
Normal file
12
src/tools/hex-converter/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Binary } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Hex Encoder/Decoder',
|
||||
path: '/hex-converter',
|
||||
description: 'Encode and decode Hex buffers to number (bits, endianess, sign or floating point or chars) and structures',
|
||||
keywords: ['hex', 'encode', 'decode', 'endianess', 'float', 'bits', 'hex', 'struct'],
|
||||
component: () => import('./hex-converter.vue'),
|
||||
icon: Binary,
|
||||
createdAt: new Date('2025-02-09'),
|
||||
});
|
|
@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter';
|
|||
import { tool as base64StringConverter } from './base64-string-converter';
|
||||
import { tool as basicAuthGenerator } from './basic-auth-generator';
|
||||
import { tool as emailNormalizer } from './email-normalizer';
|
||||
import { tool as hexConverter } from './hex-converter';
|
||||
|
||||
import { tool as asciiTextDrawer } from './ascii-text-drawer';
|
||||
|
||||
|
@ -98,6 +99,7 @@ export const toolsByCategory: ToolCategory[] = [
|
|||
components: [
|
||||
dateTimeConverter,
|
||||
baseConverter,
|
||||
hexConverter,
|
||||
romanNumeralConverter,
|
||||
base64StringConverter,
|
||||
base64FileConverter,
|
||||
|
|
|
@ -151,7 +151,7 @@ function onSearchInput() {
|
|||
>
|
||||
<div flex-1 truncate>
|
||||
<slot name="displayed-value">
|
||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full lh-normal color-current @input="onSearchInput">
|
||||
<input v-if="searchable && isOpen" ref="searchInputRef" v-model="searchQuery" type="text" placeholder="Search..." class="search-input" w-full color-current lh-normal @input="onSearchInput">
|
||||
<span v-else-if="selectedOption" lh-normal>
|
||||
{{ selectedOption.label }}
|
||||
</span>
|
||||
|
|
|
@ -39,7 +39,7 @@ const headers = computed(() => {
|
|||
<template>
|
||||
<div class="relative overflow-x-auto rounded">
|
||||
<table class="w-full border-collapse text-left text-sm text-gray-500 dark:text-gray-400" role="table" :aria-label="description">
|
||||
<thead v-if="!hideHeaders" class="bg-#ffffff uppercase text-gray-700 dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||
<thead v-if="!hideHeaders" class="bg-#ffffff text-gray-700 uppercase dark:bg-#333333 dark:text-gray-400" border-b="1px solid dark:transparent #efeff5">
|
||||
<tr>
|
||||
<th v-for="header in headers" :key="header.key" scope="col" class="px-6 py-3 text-xs">
|
||||
{{ header.label }}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue